<?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>欧洲国家快速拥抱绿色技术和电动汽车</title>
      <link>https://itindex.net/detail/63189-%E6%AC%A7%E6%B4%B2%E5%9B%BD%E5%AE%B6-%E6%8B%A5%E6%8A%B1-%E7%BB%BF%E8%89%B2</link>
      <description>因霍尔木兹海峡封锁推高世界各地的油气价格，欧洲多国转向了绿色技术购买了更多电动汽车。数据显示，3 月前三周英国热泵销量较上月同期增长 51%，太阳能销量增长 54%，电动汽车充电器销量增长 20%。法国二手车在线零售商 Aramisauto 的电动汽车销量在 2 月中旬到 3 月 9 日期间几乎翻了一番。阿姆斯特丹二手车交易平台 Olx 表示它在法国、罗马尼亚、葡萄牙和波兰的平台上电动汽车的客户咨询量激增。挪威最大二手车交易平台 Finn.no 上电动汽车销量超过了柴油车。
 &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/63189-%E6%AC%A7%E6%B4%B2%E5%9B%BD%E5%AE%B6-%E6%8B%A5%E6%8A%B1-%E7%BB%BF%E8%89%B2</guid>
      <pubDate>Thu, 02 Apr 2026 00:14:26 CST</pubDate>
    </item>
    <item>
      <title>AI Coding 的能力上限在于技术团队的工程架构能力</title>
      <link>https://itindex.net/detail/63144-ai-coding-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;div&gt;最近大家都在讨论Cursor AI写的300万行代码跑不起来的问题，虽然偶尔有嘲笑，但更多技术朋友在理性讨论：目前 AI Coding 的上限在哪。&lt;/div&gt; &lt;div&gt;在我来看，Cursor这次测试还是非常有价值的，一方面验证了当前大模型复杂度控制的实际能力，另一方面也给AI Coding领域一个警示：暴力Vibe Coding不可取。实际工作中（特别是大型工程项目）“一键成片”的时代还没有到来。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;从技术角度，Cursor这次尝试之所以没能真正跑通整个系统，表面上看，这是上下文窗口限制导致的；更本质的原因，是缺乏足够的 thinking 和 plan——也就是说，没有在架构和规划层面，预先设计出抵抗上下文限制的工程方法。&lt;/div&gt; &lt;div&gt;AI 往往是按“当前对话上下文”局部生成代码，而不是先做领域建模（核心对象、边界）、模块划分（服务拆分、层次结构）、接口协议约定（API、DTO、事件）；&lt;/div&gt; &lt;div&gt;结果是模块之间风格不统一、职责混乱，命名、数据结构、错误处理方式不一致；&lt;/div&gt; &lt;div&gt;虽然相比早期 AI Coding，循环依赖、数据流完全对不上的低级错误在工程化实践中正在减少，但调用链混乱、难以排错的问题仍然大量存在。&lt;/div&gt; &lt;div&gt;再加上大模型上下文窗口有限，导致“遗忘”和“自相矛盾”，早期定义好的接口 /数据结构，模型在后面生成时不一定记得住。这类问题在大型项目中（比如 300 万行规模下）会被无限放大，综合效应就是“跑不起来”。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;Cursor 这次实验其实是当前 Vibe Coding 思路的一种极端体现：把尽可能多的实现交给模型自动铺开。然而实际业务应用中，尤其是在 Vibe Coding 和 background agent 模式下做 AI Coding 时，统一的工程架构设计仍然非常重要。&lt;/div&gt; &lt;div&gt;这种情况下，有经验的架构师在这次AI Coding范式转型中将显得越发重要；在当下可预见的发展阶段，AI Coding 的实际效率与效果，很大程度上将取决于技术团队的工程架构能力上限。&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/63144-ai-coding-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Sun, 18 Jan 2026 15:17:01 CST</pubDate>
    </item>
    <item>
      <title>Translation of 可能是迄今为止（2025）中文互联网上，对自制力最硬核的技术讨论</title>
      <link>https://itindex.net/detail/63080-translation-of-%E4%B8%AD%E6%96%87</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;Original article translated from Chinese, written by    &lt;a href="https://www.zhihu.com/people/mount_cristo" rel="noopener noreferrer" target="_blank"&gt;edmond&lt;/a&gt;, link is    &lt;a href="https://zhuanlan.zhihu.com/p/1930893902623245386" rel="noopener noreferrer" target="_blank"&gt;https://zhuanlan.zhihu.com/p/1930893902623245386&lt;/a&gt;   &lt;br /&gt;
Translation helped with Claude and edited by myself.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;I expect this article to become the most revolutionary technical discussion on self-control in the Chinese internet (as of 2025), bar none.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Perhaps this goal seems a bit audacious, but dear readers, if you have reservations about this claim, please allow me to invite you to quickly scroll through and get a feel for the style and content of this article - perhaps it might slightly increase your confidence :)&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The core idea I want to convey in this article is: The problem of self-control need not only be a psychological or physiological problem - perhaps it can also be an engineering problem that can be solved using mathematics and physics.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— Based on the above concept, there exists such a possibility: A person can impose lasting and profound constraints on their own behavior through just a few abstract yet ingenious thought experiments, without any external deadlines, environmental constraints, or even any external supervision - even to the extent of changing the long-term stable state of their entire life.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;(Of course, it must be specially noted that even the most powerful methodology can only partially solve behavioral problems. Many severe neurological and pathological issues still need to be addressed through professional medical channels.)&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;To validate this idea, as someone who has been severely troubled by ADHD and self-control issues since childhood, I spent over a decade from elementary school through my doctoral studies, experiencing countless trials and errors, thinking and verification, gradually exploring the two generations of self-control techniques that this article will introduce.&lt;/p&gt;
 &lt;p&gt;They once helped me transform from someone who struggled long-term and couldn&amp;apos;t focus for even an hour, to being able to continuously study at home all day for several months without external pressure, with my life in well-ordered condition. The exploration process of these two generations of techniques was also an interesting and winding journey of continuous trial and iteration regarding the phenomenon of &amp;quot;self-control.&amp;quot;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Therefore, I have always considered writing this article as a mission that must be completed: I hope to offer them as a gift to those who, like my former self, are tormented by ADHD and self-control barriers. Dear strangers, I hope it can help you, and others who are suffering, even if only to solve a little bit of the troubles brought by self-control problems.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Next, I will slowly tell you about these two sets of methods. They are respectively called   &lt;strong&gt;CTDP (Chained Time-Delay Protocol)&lt;/strong&gt; and   &lt;strong&gt;RSIP (Recursive Stabilization Iteration Protocol)&lt;/strong&gt;.   &lt;strong&gt;The thinking behind these two methods is completely different from the commonly discussed concepts of &amp;quot;putting down your phone,&amp;quot; &amp;quot;making plans,&amp;quot; &amp;quot;goal decomposition,&amp;quot; &amp;quot;reward and punishment mechanisms,&amp;quot; &amp;quot;delayed gratification,&amp;quot; &amp;quot;intrinsic motivation,&amp;quot; &amp;quot;habit tracking,&amp;quot; and countless other clichéd discussions.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— What I want to do is to abstract the underlying mathematical and physical mechanisms from daily behavior, attempting to start from first principles, to solve as elegantly as possible (and partially) the age-old self-control problems of procrastination, difficulty starting, giving up midway, and low states that have plagued humanity for hundreds of years.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Of course, in this process, I will introduce some basic mathematical concepts, which may require some foundation in college-level mathematics to fully understand. But rest assured, I will try my best to use accessible popular science language, explaining concepts in a qualitative and semi-quantitative manner (quantitative analysis is also unrealistic for this topic). Additionally, to allow more friends with attention barriers like me to read through smoothly, this article will also adopt a more ADHD-friendly colloquial writing style, with illustrations interspersed, and organized using numbered paragraphs.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Finally, a small reminder: During the reading process, you will likely have various questions (for example, when reading about the Sacred Seat Principle, you might wonder &amp;quot;what if I cheat&amp;quot; or &amp;quot;what if I just refuse to sit down&amp;quot;). Please don&amp;apos;t be anxious - usually the next section will answer these questions. And many of the limitations of the first-generation method will also be discussed and resolved in the second-generation method introduced after Section 13. In short, please read slowly and don&amp;apos;t rush :)&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;CTDP Chained Time-Delay Protocol&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;Before the formal discussion, let&amp;apos;s consider a most common scenario:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Suppose it&amp;apos;s now 7 PM, you just finished dinner, sitting at your desk with homework to complete or a paper you want to read. At the same time, your phone screen lights up, and you catch a glimpse of an interesting notification on Xiaohongshu (Chinese social media). At this moment, two choices are presented to you:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;Play with your phone:&lt;/strong&gt; You&amp;apos;ll get immediate relaxation and happiness, however, this seems likely to ruin your study plan for tonight, and may later produce anxiety and regret;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Go study:&lt;/strong&gt; You&amp;apos;ll face immediate boredom and fatigue, however, this will alleviate your recent task pressure and help with your studies.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;And faced with this choice, almost everyone with poor self-control will tend to scroll through videos all night, then regret it immensely. Why is this exactly?&lt;/p&gt;
 &lt;p&gt;This question is the most classic toy model under the self-control problem. Regarding it, people have proposed various discussions - the so-called willpower model, dopamine, reward and punishment mechanisms, goal decomposition, delayed gratification, environmental control, psychological suggestion, identity recognition, to-do lists, and countless academic concepts, empirical rules, and folk remedies.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;However, in this article, I will abandon all the above commonplace, vague concepts, and instead use a simple mathematical model to explain.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;2&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;This is the first assertion of this article: When people face any choice, the true inclination toward a certain behavior can necessarily be expressed as the product of that behavior&amp;apos;s future value function&lt;/strong&gt; $V(\tau)$   &lt;strong&gt;and the weighted discount function&lt;/strong&gt; $W(\tau)$  &lt;strong&gt;, integrated from the current moment (&lt;/strong&gt; $\tau=0$   &lt;strong&gt;) to infinity:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;I=∫0∞V(τ)⋅W(τ)dτI=\int_0^{\infty}V(\tau)\cdot W(\tau)d\tauI=∫0∞​V(τ)⋅W(τ)dτ&lt;/p&gt;
 &lt;p&gt;Where:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;Future value function&lt;/strong&gt; $V(\tau)$    &lt;strong&gt;represents, in your current eyes, the value that this behavior brings at each future moment&lt;/strong&gt; $\tau$   &lt;strong&gt;;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Weighted discount function&lt;/strong&gt; $W(\tau)$    &lt;strong&gt;represents the degree of importance you attach to the value at each future moment&lt;/strong&gt; $\tau$    &lt;strong&gt;(similar to the hyperbolic discounting function in economics that describes similar phenomena).&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;In other words, when we face choices, we don&amp;apos;t simply add up all &amp;quot;future values&amp;quot; and then decide, but rather it depends on the weighted sum of values at all future moments. Generally speaking, values in the immediate future carry higher weight, while values in the distant future carry lower weight.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Value function visualization" src="https://pica.zhimg.com/v2-ec797239c6b88659933dfb05444a1ecc_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Take the example of &amp;quot;going to study vs. scrolling on phone&amp;quot; from earlier:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;If choosing to study&lt;/strong&gt;, in the short term we face switching costs and the boredom of studying, so $V(\tau)$ is negative; but in the medium term, due to the pressure relief and satisfaction from studying, $V(\tau)$ turns positive; and in the more distant future, the impact of one study session will eventually dissipate, and $V(\tau)$ approaches 0 again;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Scrolling on phone is the opposite&lt;/strong&gt;: the short-term immediate pleasure makes $V(\tau)$ positive, but in the medium term it will produce anxiety and regret from ruining plans, turning $V(\tau)$ negative; and playing on the phone for one night ultimately won&amp;apos;t change one&amp;apos;s life, so $V(\tau)$ will slowly trend toward 0.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Ideally (that is, in a purely rational situation), if the weighting function $W(\tau)$ were a constant, then the net total value of studying would obviously be higher than playing on the phone.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;However, our brains are often extremely short-sighted, with the weighting function&lt;/strong&gt; $W(\tau)$   &lt;strong&gt;being very high in the short term and rapidly approaching zero in the long term.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Under this short-sighted weight distribution, the short-term advantage of scrolling on the phone, when multiplied by the weight, gains a huge advantage, and the integration result will actually be far higher than studying. This is why we ultimately choose to play on the phone.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;(Note: In this article, we take a &amp;quot;assume without solving&amp;quot; approach to these two functions, only performing qualitative analysis, after all, quantitative analysis is not realistic for this problem)&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;3&lt;/h3&gt;
 &lt;p&gt;In fact, you&amp;apos;ll find that this seemingly simple mathematical model can explain almost all similar classic scenarios in life:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Behavioral decision cycle" src="https://pic2.zhimg.com/v2-d34e6a417ac5e72381b0d246d85e8f03_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;For example, still using the phone-playing example. You ultimately chose to pick up your phone, thinking you&amp;apos;d just play for a bit, then what happens?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;From the moment you pick up the phone, the same story repeats infinitely:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;After finishing short video A, the short-term temptation of watching short video B is again higher than putting down the phone;&lt;/li&gt;
  &lt;li&gt;After finishing short video B, the short-term temptation of watching short video C is again higher than putting down the phone...&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;From the perspective of you at every moment, $I(\text{short video}) &amp;gt; I(\text{put down phone})$ always holds true, so you will choose to watch the next short video at every moment.&lt;/p&gt;
 &lt;p&gt;So you scroll like this all night until 2 AM, 3 AM, when the cost of staying up late becomes increasingly considerable, gradually offsetting the temptation brought by short videos, and you finally go to bed filled with regret.&lt;/p&gt;
 &lt;p&gt;(Of course, some people, unable to face the anxiety and self-blame after staying up late, find the psychological cost of putting down the phone increasingly high, and end up simply staying up all night.)&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;But what if you chose to study at the beginning? You might be surprised to find that once you truly immerse yourself, continuing to study actually becomes easier and easier, and you may gradually feel less interested in scrolling on your phone.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;This is due to the psychological phenomenon of &amp;quot;switching cost&amp;quot;: When we switch from one task to another&lt;/strong&gt;, this &amp;quot;switching&amp;quot; itself is naturally accompanied by a certain psychological resistance. This is also easy to understand in this model - to extract yourself from the current activity, you must first stop what you&amp;apos;re doing, clear the working memory already loaded in your brain, and then forcibly switch to a new behavioral workflow.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;This cost, mathematically, is equivalent to inserting a negative impulse function at the most sensitive&lt;/strong&gt; $\tau=0$   &lt;strong&gt;position:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Switching cost visualization" src="https://pic1.zhimg.com/v2-817def7ded8665b9414103a62ea5524e_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;This is why it&amp;apos;s harder to climb out after falling into indulgence, but it&amp;apos;s also easier to persist once you start a task.&lt;/p&gt;
 &lt;p&gt;(To make subsequent representations more convenient, I won&amp;apos;t draw this switching cost impulse function separately, but will automatically merge it into the value function $V(\tau)$.)&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;However, sometimes we can also naturally resist the temptation to play on our phones. The week before exams and the eve of deadlines are such times.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;As mentioned earlier, the short-term pleasure brought by playing on the phone during normal times usually only causes some medium-term anxiety and won&amp;apos;t truly have a major impact on our lives.&lt;/p&gt;
 &lt;p&gt;But exam week is different - if you&amp;apos;re still addicted to short videos at this time, you&amp;apos;ll fail the exam, producing a series of serious consequences, which might really change your life.&lt;/p&gt;
 &lt;p&gt;Thus, the long-term negative value of playing on the phone swells dramatically, brutally defeating the short-term temptation with extremely low weight! Therefore, the script for review week is usually a clear threshold: after a certain time point, your time investment suddenly increases sharply. The so-called &amp;quot;deadlines are the first productivity&amp;quot; actually follows this principle.&lt;/p&gt;
 &lt;p&gt;(Of course, for some people, the severity of these consequences also makes &amp;quot;going to review&amp;quot; increasingly heavy in meaning, increasing switching costs. The closer to the deadline, the less they can start, and they really do end up failing.)&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Examples like this are numerous. It can be said that for individual behaviors, the success or failure of our self-control depends on one thing - whether the distribution of the value distribution function&lt;/strong&gt; $V(\tau)$   &lt;strong&gt;over time is favorable.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;4&lt;/h3&gt;
 &lt;p&gt;Since $W(\tau)$ represents humanity&amp;apos;s innate &amp;quot;short-sightedness,&amp;quot; which is nearly fixed and very difficult for us to fundamentally change, we must ask a question: How is self-control even &amp;quot;  &lt;strong&gt;possible&lt;/strong&gt;&amp;quot;?&lt;/p&gt;
 &lt;p&gt;Here, we can make the second assertion of this article:&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;All effective self-control strategies for humanity are essentially constructing a transformation of the value distribution function&lt;/strong&gt; $V(\tau)\rightarrow V&amp;apos;(\tau)$  &lt;strong&gt;, thereby making behavioral tendencies closer to the results of rational decision-making.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Let&amp;apos;s give a few examples:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;Useless Method 1 (Long-term Rewards)&lt;/strong&gt;: Self-motivation, imagining &amp;quot;the beautiful life after future success,&amp;quot; or doing &amp;quot;gamification,&amp;quot; setting rewards for yourself after completing study. This is equivalent to adding a positive linear incentive in the distant future to the value function $V(\tau)$ of studying - but because the long-term weight is extremely low, this method actually doesn&amp;apos;t work well and is usually useless;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Useless Method 2 (Long-term Punishment)&lt;/strong&gt;: Setting punishments for playing on your phone, or going for a run or writing a self-criticism after playing on your phone. This is equivalent to inserting a negative value in the long-term future of the value function $V(\tau)$ of playing on phone - this method is equally useless;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Slightly Useful Method 3 (Near-term Punishment)&lt;/strong&gt;: Locking up your phone or finding someone to supervise you. This is equivalent to increasing the switching cost of indulgent behavior in the near term, i.e., inserting negative values in the near term of its $V(\tau)$ - this method is useful, but not much;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Useful Method 4 (Nonlinear Compression)&lt;/strong&gt;: For example, the &amp;quot;Pomodoro Technique&amp;quot; familiar to many. Its mechanism is actually to transform the $V(\tau)$ of &amp;quot;giving up midway&amp;quot; after starting to study, bundling and binding the sunk cost of the entire Pomodoro timer, then nonlinearly compressing it to the current moment - this is a relatively useful method, and we&amp;apos;ll elaborate on it in detail when discussing CTDP (first-generation technique) later.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;To more intuitively measure the effectiveness of these methods, we can also define a metric, which is the gain (G) of self-control strategies:&lt;/p&gt;
 &lt;p&gt;G=I′(study)I′(indulge)/I(study)I(indulge)G=\frac{I&amp;apos;(\text{study})}{I&amp;apos;(\text{indulge})}/\frac{I(\text{study})}{I(\text{indulge})}G=I′(indulge)I′(study)​/I(indulge)I(study)​&lt;/p&gt;
 &lt;p&gt;Simply put, it&amp;apos;s the ratio of rational decision-making tendency before and after using the strategy. If we use this metric to test the vast majority of mainstream so-called &amp;quot;self-control methods&amp;quot; on the market, you&amp;apos;ll find their gains are pitifully low.   &lt;strong&gt;They either only work at the far end where weight is extremely low, or don&amp;apos;t even act on the value function&lt;/strong&gt; $V(\tau)$   &lt;strong&gt;at all:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;For example, &amp;quot;Just do it,&amp;quot; &amp;quot;Tell yourself you always have a choice,&amp;quot; these types of marketing slogans and motivational clichés can actually get hundreds of thousands of likes on Zhihu and Douyin, showing how low the average cognitive level is regarding self-control topics.&lt;/p&gt;
 &lt;p&gt;And those who can achieve self-control through low-efficiency means (many of these people are indeed excellent) - it&amp;apos;s not because these methods are so outstanding, but because they already have advantageous habits, environment, and personal conditions, only one step away from true self-control, so even weak stimulation can push them toward positive behavior.&lt;/p&gt;
 &lt;p&gt;The sad thing is, precisely because these advantages actually account for an extremely high proportion in personal achievement, people with these advantages rarely need to pursue truly efficient self-control strategies. The superficial methods they share after success have instead become the most widely spread mainstream cognition - this counterintuitive survivorship bias will be discussed more deeply at the end of the article.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;5&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Now, here comes the interesting part.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;With this mathematical foundation, we can construct an extremely ingenious strategy that, through nonlinear compression and linear translation transformations of&lt;/strong&gt; $V(\tau)$   &lt;strong&gt;at different time points, can almost conjure up astonishing positive gains for individual rational behaviors out of thin air.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;More importantly, it can solve in one stroke the three most common ailments we face in self-control:   &lt;strong&gt;giving up midway, the broken window effect, and difficulty starting.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;And all of this is built on three core principles.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The first core principle is called the &amp;quot;Sacred Seat Principle.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Let&amp;apos;s do such a thought experiment:&lt;/p&gt;
 &lt;p&gt;Suppose in a self-study room, there are many seats for you to choose from. One day, you have a whim and designate one seat as the &amp;quot;Sacred Seat,&amp;quot; and establish such a game rule for it:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;Sitting in any other seat has no special constraints, you can do whatever you want; but as long as your buttocks touch this &amp;quot;Sacred Seat,&amp;quot; you must use your best state to focus and study for a full hour. Conversely, if you don&amp;apos;t have confidence in achieving focus, then simply don&amp;apos;t allow yourself to sit in this seat - rather choose other ordinary seats.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;In short, this &amp;quot;Sacred Seat&amp;quot; must never be defiled by an unfocused buttocks.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Of course, merely imagining such a rule doesn&amp;apos;t inherently possess any real binding force. But what if you really seriously executed it once?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;One day, you really sat down, and as soon as your buttocks touched that seat, you really used your most serious state to study for an hour.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;A magical thing happened: From the moment you first successfully executed this rule, this seat that was originally just imagined truly became valued in your heart! From then on, your possibility of sitting in this seat and casually playing on your phone would truly be much lower than before:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;At this time, you add a new game rule:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;Record the first focused session as #1. Henceforth, every time you successfully focus for an hour, it can serve as proof of work, adding a numbered record to this &amp;quot;Sacred Seat&amp;quot;: #1, #2, ...#N. But as long as there&amp;apos;s one failure - for example, if you scrolled on your phone while sitting there, or only sat for ten minutes before leaving - then all records will be cleared to zero, and next time you can only start over from #1.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;As this chain continues to extend and proof of work continues to accumulate&lt;/strong&gt;, the value of this virtual seat will strengthen time after time. When the node count of this task chain grows to   &lt;strong&gt;#10, #20, #30&lt;/strong&gt;, the binding force of this rule simply becomes tangible - you may even become cautious, not even daring to breathe heavily, lest there be even the slightest disrespect to the rule.&lt;/p&gt;
 &lt;p&gt;Note: &amp;quot;Sacred Seat&amp;quot; is just one example. In fact, it can be any symbolic object - a clipboard, a hat, a message to a WeChat account can all be a Sacred Seat. But it&amp;apos;s best to be something visible and tangible. For example, the Sacred Seat I currently use is a blue hat - as long as I wear it, I must focus for an hour.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;(The clever reader has surely thought of the problem of this rule collapsing + you being unwilling to sit down. Don&amp;apos;t worry, this is precisely what sections 8 and 9 will solve.)&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;(To avoid many people&amp;apos;s misunderstanding, I need to state here that this is not the final version of the method. What truly works is the mathematical mechanism, and it has nothing to do with so-called &amp;quot;morality,&amp;quot; &amp;quot;ritual sense,&amp;quot; &amp;quot;psychological suggestion,&amp;quot; or &amp;quot;now or never.&amp;quot; This will be explained in detail later.)&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;6&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;This magical binding force actually comes from humanity&amp;apos;s innate obsession with &amp;quot;maintaining records.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Many fitness or learning apps have a &amp;quot;consecutive check-in&amp;quot; feature. Many people, no matter how tired or exhausted, will force themselves to memorize vocabulary for 5 minutes just to maintain that &amp;quot;365-day consecutive check-in&amp;quot; record on Duolingo. Someone who has quit smoking or drinking for 10 consecutive days will find it harder to give up seeing that number 10 than on the first day.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— Just an imagined record is enough to produce absurdly strong binding force.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Looking closely, this binding force actually comes from two points:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;On one hand, the longer the record is maintained, the more real time and energy you&amp;apos;ve invested to maintain this record. Behind each successful task node in the chain is real &amp;quot;proof of work&amp;quot; and sunk cost;&lt;/li&gt;
  &lt;li&gt;On the other hand, this record is often accompanied by a subtle &amp;quot;future value expectation&amp;quot;: You think this record has value → You&amp;apos;re afraid of losing this record → It generates binding force → This binding force helps with your future self-control, and your future self-control relies on it → This record becomes more valuable. The higher the value, the stronger the binding force; the stronger the binding force, the higher the future expectation; the higher the future expectation, the higher the value...&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;However, all &amp;quot;records&amp;quot; naturally have the characteristic of &amp;quot;all or nothing&amp;quot;: Once you break this record, all the sunk costs and future expectations accumulated through this hard work will immediately and suddenly be completely lost at the moment&lt;/strong&gt; $\tau=0$  &lt;strong&gt;:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Value compression at failure point" src="https://pic1.zhimg.com/v2-733e696e51bf2761fe5c62b6fc665bf8_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;This is the mathematical essence behind the &amp;quot;Sacred Seat Principle&amp;quot;:   &lt;strong&gt;Nonlinear compression transformation of&lt;/strong&gt; $V(\tau)$  &lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;When you sit in this seat, all the value invested in all nodes of the entire task chain in the past, and the expected value of the future, will be dramatically compressed in the value function of the &amp;quot;giving up focus&amp;quot; option, condensed into a peak extremely close to the origin (&lt;/strong&gt; $\tau=0$   &lt;strong&gt;) - any short-term temptation to break the rule must actually immediately face the challenge of the entire chain&amp;apos;s accumulated and future value.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And the most wonderful thing is that this holds true at every moment of the focus task. When the sunk cost accumulates to a certain degree, there will no longer be any short-term temptation that can challenge such an astonishing barrier.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;7&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;This &amp;quot;all or nothing&amp;quot; value binding principle is also why many classic self-control strategies truly work:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;For example, the Pomodoro Technique - each Pomodoro timer is actually equivalent to a &amp;quot;small Sacred Seat&amp;quot;: It bundles the sunk cost and future expectation of the entire focus period into one whole &amp;quot;tomato.&amp;quot; Once you slack off or give up during the Pomodoro period, you immediately bear the huge cost of losing the entire &amp;quot;tomato&amp;quot; at the current instant.&lt;/p&gt;
 &lt;p&gt;This way, your choice at every moment is no longer a comparison between &amp;quot;immediate temptation and current task,&amp;quot; but becomes a competition between &amp;quot;immediate temptation and the total compressed tomato value&amp;quot; - this is the real core mechanism behind why the Pomodoro Technique truly works.&lt;/p&gt;
 &lt;p&gt;There are many more such examples. Many people have also had such experiences:&lt;/p&gt;
 &lt;p&gt;One day you learned a brand new self-control method, felt it made a lot of sense, and enthusiastically invested in practice. At the beginning, even if that method is actually useless, you&amp;apos;ll find it works like magic.&lt;/p&gt;
 &lt;p&gt;But strangely, after a few days, the initial novelty gradually fades, you begin to violate the rules frequently, the effectiveness of the method rapidly declines, and ultimately completely fails and is abandoned by you.&lt;/p&gt;
 &lt;p&gt;This is a kind of illusion similar to a &amp;quot;beginner&amp;apos;s grace period&amp;quot;:   &lt;strong&gt;Any self-control method appears to be useful in the first few days, but what truly works may not be the method itself, but rather your &amp;quot;future expectation&amp;quot; of this method.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;When you entrust your future self-control hopes to this method, this &amp;quot;entrustment&amp;quot; briefly truly grants it binding force.&lt;/strong&gt; However, if the method itself is ineffective, this temporary binding force ultimately cannot support it to survive long-term in the complex real environment. Once any single violation or wear occurs, the entire method&amp;apos;s credibility and value will quickly collapse with the broken window effect.&lt;/p&gt;
 &lt;p&gt;The most beautiful aspect of the &amp;quot;Sacred Seat&amp;quot; design is that it is naturally a   &lt;strong&gt;distributed, decentralized&lt;/strong&gt; design in terms of time. It is only responsible for those selected, purified states of sitting in that seat, without needing to be exposed to all time or be responsible for long-term states, thereby maximally avoiding wear.&lt;/p&gt;
 &lt;p&gt;In other words, you can go to parties, drink, play games, waste several days or even a week after completing task #1 in full state, but when you next walk back into the self-study room to start #2, the Sacred Seat is still that Sacred Seat, and its binding force won&amp;apos;t weaken in the slightest.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;8&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Of course, even within this selected state, the &amp;quot;Sacred Seat Principle&amp;quot; is not completely without loopholes.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;As mentioned earlier, once you sit in this position, you must study in &amp;quot;the best state&amp;quot; for an hour. So the question arises: how do we define this &amp;quot;best state&amp;quot;?&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;If I go to the bathroom midway, does it still count as &amp;quot;the best state&amp;quot;?&lt;/li&gt;
  &lt;li&gt;If I answer a phone call or pick up a delivery, does it still count as &amp;quot;the best state&amp;quot;?&lt;/li&gt;
  &lt;li&gt;If someone messages me and I reply, does it still count as &amp;quot;the best state&amp;quot;?&lt;/li&gt;
  &lt;li&gt;If all of these count, then scrolling on my phone midway and watching a couple of short videos should also count as &amp;quot;the best state,&amp;quot; right?&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;You&amp;apos;ll find that the &amp;quot;ideal state&amp;quot; simply doesn&amp;apos;t exist. Once any self-control strategy enters actual combat, it must face complex and variable real situations. On the surface, a self-control strategy is just a simple constraint; but in actual application,   &lt;strong&gt;all self-control strategies are equivalent to numerous implicit, tiny &amp;quot;sub-constraints,&amp;quot;&lt;/strong&gt; and each sub-constraint can be tested and challenged:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;If you allow yourself flexible discretion, the method&amp;apos;s binding force will be worn away bit by bit in one &amp;quot;this time is special, not to be taken as precedent&amp;quot; rationalization after another, producing the broken window effect, ultimately being eroded full of holes;&lt;/li&gt;
  &lt;li&gt;But if you don&amp;apos;t allow any exceptions at all, this method becomes rigid and brittle. When you encounter a situation where you absolutely must make an exception, the rule may instantly and completely collapse.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;This &amp;quot;gradual broken window effect&amp;quot; is precisely the fundamental reason why the vast majority of methods similar to &amp;quot;gamification&amp;quot; or &amp;quot;establishing rules&amp;quot; eventually fail. What matters is not what game the athletes play, but the logic of separating athletes from referees.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;To solve this problem, let&amp;apos;s introduce a more ingenious second core principle: the &amp;quot;Precedent-Setting Principle.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;(Note: it&amp;apos;s &amp;quot;Precedent-Setting&amp;quot; (bì), not &amp;quot;not to be taken as precedent&amp;quot; (bù))&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Since you yourself also know clearly that once there&amp;apos;s a first &amp;quot;not to be taken as precedent,&amp;quot; there will inevitably be countless subsequent violations, then why not do the opposite and forcibly require yourself to &amp;quot;set a precedent&amp;quot; - that is,   &lt;strong&gt;conversely requiring yourself to violate in the future!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Specifically, when you face any judgment that seems like a violation, like the &amp;quot;case law&amp;quot; in Western legal systems, you can only choose one of the following two options:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Immediately judge that the &amp;quot;Sacred Seat&amp;quot; rule is completely broken, the chain is severed, thoroughly clear all task records, admit the binding force has completely returned to zero; next time honestly start over from #1;&lt;/li&gt;
  &lt;li&gt;Judge that the current behavior is allowed, but as long as it&amp;apos;s allowed this once, the same situation must be allowed in the future; for the entire subsequent lifecycle of this task chain, it will thoroughly lose binding force over this behavior.&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;strong&gt;Your &amp;quot;best state&amp;quot; is not defined by any subjective or objective standard, but is dynamically formed by countless &amp;quot;precedents&amp;quot;:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Going to the bathroom midway, does it still count as &amp;quot;the best state&amp;quot;? It can count, but as long as it counts this time, it must always count in the future;&lt;/li&gt;
  &lt;li&gt;Replying to messages midway, does it still count as &amp;quot;the best state&amp;quot;? It can count, but as long as it counts this time, it must always count in the future;&lt;/li&gt;
  &lt;li&gt;Scrolling on your phone for two minutes midway, does it still count as &amp;quot;the best state&amp;quot;? It can count, but as long as it counts this time, it must always count in the future;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;This way, what you&amp;apos;re weighing is no longer a single isolated choice, but whether to permanently give up the rule&amp;apos;s binding force over this behavior at this current instant. The cost presented to you is   &lt;strong&gt;the real long-term cost of allowing this behavior.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Now in your eyes:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;For behaviors that rationally shouldn&amp;apos;t be allowed (such as playing on your phone), you sitting in the seat are very clear: as long as you allow this &amp;quot;exception&amp;quot; today, then every time you sit in this seat in the future, you&amp;apos;ll cite this behavior as a &amp;quot;precedent&amp;quot; to violate (moreover, the rule requires you to violate), and you can never redefine this behavior as a violation;&lt;/li&gt;
  &lt;li&gt;But for situations that rationally should be allowed (such as going to the bathroom), your future self can also feel at ease giving yourself permission, without any concerns about the rule&amp;apos;s credibility.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Ultimately, the decisions you make truly become the most aligned with long-term rationality. Because you thinking of the present (as the athlete) and you thinking of the future (as the referee) have already reached consensus under this mechanism in a wonderful way -   &lt;strong&gt;just like this&lt;/strong&gt;,   &lt;strong&gt;you yourself have reached a Nash equilibrium across time with yourself.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;When this method runs long-term, the rule&amp;apos;s binding boundaries won&amp;apos;t gradually erode and collapse like traditional methods, but rather slowly and $\epsilon-n$ style, with a precision of &amp;quot;always just enough,&amp;quot; gradually converge to the boundary closest to rational decision-making: both able to tolerate truly necessary exceptions and effectively prevent unnecessary self-indulgence.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;This is the ingenious aspect of the &amp;quot;Precedent-Setting Principle.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;9&lt;/h3&gt;
 &lt;p&gt;Alright, now we have a perfect &amp;quot;Sacred Seat.&amp;quot; But a new problem arises:&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The more sacred the seat, the less you dare to sit in it.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Indeed, the state of sitting in this &amp;quot;Sacred Seat&amp;quot; is very perfect. However, this seat is too sacred, too perfect, so much so that the commitment of the action &amp;quot;sitting in the seat&amp;quot; is too heavy, and you&amp;apos;ll become increasingly unwilling to sit down - that is, the commonly mentioned &amp;quot;difficulty starting&amp;quot; problem.&lt;/p&gt;
 &lt;p&gt;This is precisely why people always like to say &amp;quot;perfectionism leads to procrastination&amp;quot; (although strictly speaking, the essence of procrastination isn&amp;apos;t perfectionism, but rather the rise in switching costs caused by overly high expectations).&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;At this point, it&amp;apos;s time for our third core principle: the &amp;quot;Linear Time-Delay Principle.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And this principle, from a mathematical perspective, can elegantly solve in one stroke the &amp;quot;procrastination&amp;quot; problem that has plagued countless people for so long.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Let&amp;apos;s do another simple thought experiment:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Suppose you find a typical procrastinator and ask them: &amp;quot;Are you willing to start studying right now?&amp;quot; They&amp;apos;ll mostly shake their head and refuse;&lt;/li&gt;
  &lt;li&gt;But if you change the way you ask: &amp;quot;Then are you willing to start studying tomorrow afternoon?&amp;quot; You don&amp;apos;t even need to wait that long: &amp;quot;How about starting in 15 minutes?&amp;quot;    &lt;strong&gt;This time, they&amp;apos;ll surprisingly mostly agree! And the longer this delay, the higher the probability this person will agree.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;Why does this strange phenomenon occur?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Let&amp;apos;s return to the mathematical model from earlier to explain:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Time-delay principle visualization" src="https://pic2.zhimg.com/v2-c84e00a3997348c03ed6a4502bd94549_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;When you consider &amp;quot;whether to start studying right now,&amp;quot; just as analyzed earlier, the negative value of the value function $V(\tau)$ happens to be at the high-weight near term of the weighting function $W(\tau)$, so you&amp;apos;ll feel strong resistance;&lt;/li&gt;
  &lt;li&gt;However, when you consider &amp;quot;whether you&amp;apos;re willing to start studying in 15 minutes,&amp;quot; the situation is completely different: This actually amounts to shifting $V(\tau)$    &lt;strong&gt;backward on the time axis by 15 minutes&lt;/strong&gt;,    &lt;strong&gt;arriving at&lt;/strong&gt; $W(\tau)$    &lt;strong&gt;&amp;apos;s relatively flat region! Compared to the option of indulgence, this almost smooths out its huge disadvantage in the short term.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;This also explains a very common phenomenon in daily life: we always harbor blindly optimistic fantasies about our future self-control.&lt;/p&gt;
 &lt;p&gt;In the hyperbolic discounting function, weight decreases over time first steeply, then gradually - the weight 10 minutes from now and the weight 1 hour from now might differ vastly, but the weight 1 day plus 10 minutes from now and the weight 1 day plus 1 hour from now differ almost negligibly.&lt;/p&gt;
 &lt;p&gt;When we consider future choices in advance, it&amp;apos;s equivalent to integrating using $V(\tau-\Delta\tau)$ after shifting, then with $W(\tau)$. The longer the shift distance, the flatter the $W(\tau)$ actually participating in integration, and naturally the closer to the rational state. This is why we&amp;apos;re often blindly optimistic when making summer vacation plans, naively thinking we can really put down our phones after just five minutes of scrolling.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And here comes the climax of this method:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;We can establish a parallel &amp;quot;auxiliary chain&amp;quot; in addition to the main task chain,   &lt;strong&gt;also protected by the Sacred Seat Principle, also &amp;quot;setting precedent&amp;quot; for all situations&lt;/strong&gt;, and its contained binding rule is very simple:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Set a simple action as a reservation signal, such as snapping your fingers once;&lt;/li&gt;
  &lt;li&gt;Once this signal is triggered, you must - within the next 15 minutes -    &lt;strong&gt;sit! down! in! that! Sacred! Seat!&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;As the saying goes, better to dredge than to block. The best way to overcome procrastination is to first admit and respect procrastination.&lt;/p&gt;
 &lt;p&gt;Since the threshold for &amp;quot;reserving to start in 15 minutes&amp;quot; is far lower than the threshold for &amp;quot;starting immediately,&amp;quot; your present self can start the reservation without any pressure.&lt;/p&gt;
 &lt;p&gt;15 minutes later, that auxiliary chain&amp;apos;s past and future value will gradually compress at $\tau=0$, condensing into a sharp, sky-piercing needle - forcibly puncturing that door of &amp;quot;switching cost&amp;quot; and &amp;quot;starting difficulty.&amp;quot;&lt;/p&gt;
 &lt;p&gt;(By the way, a fun fact: When I first came up with this method, one night I couldn&amp;apos;t sleep and accidentally snapped my fingers, so I had no choice but to reluctantly climb up at 3 AM to study for an hour.)&lt;/p&gt;
 &lt;p&gt;Now, combining the three core principles above, we finally have the complete first-generation self-control technique:&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Chained Time-Delay Protocol (CTDP)&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;10&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Here is the complete description of the Chained Time-Delay Protocol (CTDP):&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;CTDP is a behavioral constraint strategy constructed based on three core principles (Sacred Seat Principle, Precedent-Setting Principle, Linear Time-Delay Principle). Specifically, it requires you to build two parallel task chains (main chain and auxiliary chain), and strictly follow these steps:&lt;/p&gt;
 &lt;h6&gt;  &lt;strong&gt;Main Chain (Task Chain):&lt;/strong&gt;&lt;/h6&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;strong&gt;First, designate a specific object as a marker, as the &amp;quot;Sacred Seat&amp;quot;&lt;/strong&gt; (In fact, the Sacred Seat is just a metaphor; it can be anything - a specific chair, a special pen, a hat, or even a message sent to your specific WeChat account)&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Once you trigger this marker, you must complete a clear focus task in &amp;quot;the best state&amp;quot;;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Each time you successfully complete a focus task, you can record a node in the main chain: the first success is #1, the second success is #2, the third is #3, and so on;&lt;/li&gt;
  &lt;li&gt;If at any point midway through a task you seem to have done something inconsistent with &amp;quot;the best state,&amp;quot; you must choose one of the following two options (&amp;quot;Precedent-Setting&amp;quot; principle):&lt;/li&gt;
  &lt;li&gt;Judge that the entire main task chain immediately fails, clear all currently accumulated node records, and next time you can only start over from #1;&lt;/li&gt;
  &lt;li&gt;Judge that the current behavior is allowed, but from now on, this behavior must also be permanently allowed in subsequent tasks and can no longer be considered a violation;&lt;/li&gt;
&lt;/ol&gt;
 &lt;h6&gt;  &lt;strong&gt;Auxiliary Chain (Reservation Chain):&lt;/strong&gt;&lt;/h6&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;strong&gt;Define a simple reservation signal, such as snapping your fingers, starting an alarm, indicating that the main task will start in 15 minutes;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Once you trigger this reservation signal, within the next 15 minutes, you must go trigger the marker corresponding to that Sacred Seat and start a main chain task;&lt;/li&gt;
  &lt;li&gt;If after triggering the reservation, you don&amp;apos;t trigger that marker within 15 minutes, then the &amp;quot;Precedent-Setting&amp;quot; principle also applies:&lt;/li&gt;
  &lt;li&gt;Either thoroughly clear the reservation chain&amp;apos;s records and admit the reservation chain has failed;&lt;/li&gt;
  &lt;li&gt;Or allow the current situation, but from now on, the reservation chain will thoroughly lose all binding force over this situation;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;strong&gt;Thus, by simultaneously using nonlinear value compression (Sacred Seat) + case law constraint (Precedent-Setting) + linear time translation (reservation mechanism) through three ingenious mechanisms, we hack away the influence of starting difficulty, broken window effect, and short-sighted decision-making.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Through just a few thought experiments, we&amp;apos;ve constructed a self-control strategy that can nearly conjure up huge gains for rational behavior out of thin air, without external supervision or even insufficient willpower. Moreover, its binding force comes entirely from proof of work at each node, only responsible for these distributed, selected nodes, and won&amp;apos;t be eroded by exposure to long-term state fluctuations.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;By relying on the CTDP strategy, for any important task, we can achieve both easy starting and easy persistence, without losing effectiveness in the long term - this seemingly impossible triangle, we want it all.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;11&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Of course, the CTDP described earlier is more of an idealized version. In real life, its application is far more flexible and interesting than what was just described.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;For example,   &lt;strong&gt;does the &amp;quot;Sacred Seat&amp;quot; really have to be a seat?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Actually not necessarily. It can be any specific, easily distinguishable marker. For example, the marker I usually use is a dedicated WeChat account. Every time I start a task, I send a message to trigger the task while also recording nodes and declaring goals;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Secondly, the task chain itself doesn&amp;apos;t necessarily have to be strict linear progression (#1, #2, #3...). You can completely construct a top-down hierarchical organization:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;For example, three unit-level nodes can form a 3-hour task group, recorded as ##1;&lt;/li&gt;
  &lt;li&gt;Three task groups form a day-level task cluster, recorded as ###1;&lt;/li&gt;
  &lt;li&gt;Three task clusters form a three-day-level task formation, recorded as ####1;&lt;/li&gt;
  &lt;li&gt;Two to three formations can be managed with a week-level task ensemble, recorded as #####1;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;And each unit can have its own requirements. For example, a ## task group can require that after completing the first two # units, a reservation signal must be executed once, which can string together three # units. This way, we&amp;apos;re like a military command system, organizing huge task chains in a top-down &amp;quot;rule of three&amp;quot; manner:&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Similarly, the content of task units doesn&amp;apos;t necessarily have to be monotonous focused studying. To use a metaphor, it can also be divided into different &amp;quot;troop types.&amp;quot;&lt;/p&gt;
 &lt;p&gt;For example, studying, doing experiments, reading papers = &amp;quot;assault units&amp;quot;; information gathering = &amp;quot;reconnaissance units&amp;quot;; making plans = &amp;quot;command units&amp;quot;; handling miscellaneous matters = &amp;quot;special service units&amp;quot;; exercise and training = &amp;quot;engineering units&amp;quot;; preparing and cooking meals = &amp;quot;cooking units&amp;quot;...&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And a large task cluster or task formation can be like a modern combined arms team, composed of multiple troop types -&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A main battle task cluster focused on work can be synthesized from 7 assault units + 2 reconnaissance units;&lt;/p&gt;
 &lt;p&gt;A weekend rest logistics task cluster can be 1 command unit + 3 special service units + 2 engineering units + 1 cooking unit synthesis;&lt;/p&gt;
 &lt;p&gt;During vacation, a three-day formation that balances exercise and self-study can be synthesized according to 6 assault groups + 3 engineering groups.&lt;/p&gt;
 &lt;p&gt;You&amp;apos;ll find that this task chain structure naturally accomplishes what people often call &amp;quot;goal decomposition.&amp;quot; Meanwhile, &amp;quot;gamification&amp;quot; doesn&amp;apos;t need to be deliberately designed - because when you truly face a large task, the style is like this:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Military-style task deployment" src="https://pica.zhimg.com/v2-deeb10447bc28767c174b0a900dd8220_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Diary, take note, I make the following deployment adjustments:&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;With the fourth task cluster, plus the eleventh task cluster and fifteenth and sixteenth two independent reconnaissance groups, strengthen next week&amp;apos;s assignment deadline defense line;&lt;/strong&gt;   &lt;br /&gt;
   &lt;strong&gt;Second, third, seventh, eighth, ninth five task clusters, plus the sixth task cluster&amp;apos;s seventeenth assault group, concentrate forces to complete notes;&lt;/strong&gt;   &lt;br /&gt;
   &lt;strong&gt;Tenth assault group plus one assault group, on the TOEFL and GRE frontline, blocking vocabulary arriving at review time;&lt;/strong&gt;   &lt;br /&gt;
   &lt;strong&gt;Twelfth task cluster cooperates with twelve independent units to besiege previously found knowledge gaps;&lt;/strong&gt;   &lt;br /&gt;
   &lt;strong&gt;Fifth task cluster and sixth task cluster&amp;apos;s two reconnaissance groups search for materials;&lt;/strong&gt;   &lt;br /&gt;
   &lt;strong&gt;Fourteenth task cluster as total reserve, don&amp;apos;t move;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;Some additional notes:&lt;/p&gt;
 &lt;p&gt;In practice, using only a simple reservation signal is still too fragile because sometimes when the 15min is up, I might be in the bathroom/outdoors, and starting a task isn&amp;apos;t realistic. So I&amp;apos;ll design two startup signals as a buffer:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Reservation startup signal: The signal is snapping fingers once. Once triggered, the &amp;quot;immediate startup signal&amp;quot; must be executed within 14min30s (leaving a buffer to prevent really using up 15min);&lt;/li&gt;
  &lt;li&gt;Immediate startup signal: The signal is snapping fingers three times. Once triggered, you must start the task as soon as possible according to available conditions.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Additionally, this strategy of &amp;quot;Sacred Seat + Precedent-Setting + Linear Time-Delay&amp;quot; combined can also be easily extended to any aspect of life:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;For example, to initiate running exercise, you can use a specific gesture as a reservation signal - doing n repetitions of the movement means you must run for 5×n minutes;&lt;/li&gt;
  &lt;li&gt;To address the common shower and going-out procrastination problems for ADHD populations, you can also specifically set corresponding shower reservation signals and going-out reservation signals;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Extending it further, you can almost &amp;quot;remotely control&amp;quot; your daily life directly with simple gestures or movements - in an extremely easy and elegant way, thoroughly eliminating procrastination problems.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;12&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Up to this point, the content of the first-generation self-control technique has been completely presented.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Looking back, this technique, whether from principle design, practical implementation, or the ingenuity of the method, has far surpassed those superficial motivational slogans, to-do checklists, or gamification designs on the market.&lt;/p&gt;
 &lt;p&gt;Indeed, in the several years after CTDP&amp;apos;s birth, it produced astonishing effects on me personally.&lt;/p&gt;
 &lt;p&gt;I must honestly say that my own self-control foundation is really quite terrible: from elementary school to high school, I was deeply addicted to games day and night, plagued by ADHD for years, with study habits and life order in complete chaos. From childhood to adulthood, I couldn&amp;apos;t listen to even one class, and ultimately tested into a 985 university. In early university, after the environment relaxed, I could even be distracted during exam week, unable to review even half-heartedly. The example mentioned earlier about getting closer to DDL but becoming even less able to study was once my real self - truly at the bottom in terms of self-control foundation.&lt;/p&gt;
 &lt;p&gt;But since CTDP&amp;apos;s birth, I achieved for the first time in my life sustained self-discipline lasting weeks or even months. With this technique, I could extremely easily start tasks and maintain high focus throughout the entire day without burden. ADHD problems seemed to be alleviated overnight. Not only did my late university grades improve significantly, I even achieved study abroad exchange, published papers, successfully conquered TOEFL and GRE, and ultimately completed a hardcore master&amp;apos;s program.&lt;/p&gt;
 &lt;p&gt;Especially during peak state periods (such as while preparing for TOEFL and GRE), it could even let me efficiently mobilize 8-10 hours daily for two consecutive months. Fighting with large armies of dozens of task ensembles and hundreds of task units, all could move at command, advance and retreat in order, seamless as flowing clouds and water.&lt;/p&gt;
 &lt;p&gt;There was even once when I caught a cold for three days midway through exam review, and I could still calmly calculate its impact on the exam fifteen days later, and calmly deploy eight or nine ### task groups from the total reserve deployed seven days later to fill the gap.&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Exam preparation schedule example" src="https://pic1.zhimg.com/v2-db1ca85ac2e8b67e948968bd7a07dbda_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;(For example, this is a plan from over a month before a certain exam, where each unit cell represents one ### level task group)&lt;/p&gt;
 &lt;p&gt;Faced with such unprecedented efficient self-discipline, I was once optimistically under the impression that the mansion of self-control had already been built, and what remained was just minor repairs and patches.&lt;/p&gt;
 &lt;p&gt;However, I soon discovered that CTDP wasn&amp;apos;t useful at all times. Specifically, I observed a clear bipolarization phenomenon:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;When the overall state was already favorable for self-control, especially when there were clear pressures and goals like DDL, such as during pre-exam review periods or times with many urgent tasks, CTDP could indeed maximize the use of these pressures, making self-control and discipline reach unprecedented heights, with almost every hour flowing like water, at my command;&lt;/li&gt;
  &lt;li&gt;However, when the overall state was originally unfavorable for self-control, such as during idle time at home, physical and mental fatigue, or without clear task goals, I often extremely lacked even the willingness to trigger the reservation startup signal. Even after forcibly starting multiple times, it would collapse after just a few # tasks.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Therefore, in the following three or four years, I tried countless times to improve and upgrade it.   &lt;strong&gt;However, CTDP seemed to have exhausted the limits of self-control strategies from the microscopic perspective of &amp;quot;individual behaviors at the 1-hour scale.&amp;quot; No matter how I modified it, over the years it could never advance even an inch, and self-control state always showed strong stage-dependency and environmental dependency.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Just like this, this bottleneck troubled me for several years. Until five years later, I finally found a deeper perspective to explain all of this&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— Not focusing on the traditional &amp;quot;motivation,&amp;quot; &amp;quot;rewards and punishments,&amp;quot; or &amp;quot;constraints,&amp;quot; but using the perspective of &amp;quot;scale&amp;quot; to view the entire system!&lt;/strong&gt;&lt;/p&gt;
 &lt;h3&gt;13&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Under this new perspective, the various behaviors in our lives are not composed of simple, isolated, single decision nodes painted solely by the immediate&lt;/strong&gt; $V(\tau)$  &lt;strong&gt;. In reality, it&amp;apos;s more like a &amp;quot;behavioral tree&amp;quot; composed of countless continuous, interwoven decision nodes.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;In this behavioral tree, the direction of each node highly depends on the choices of the nodes before it; and its macroscopic direction at various time scales is highly determined by various large and small scale factors in life.&lt;/strong&gt; And among them, the vast majority of nodes are simply not suitable for our limited free will, or self-control strategies driven by free will, to intervene.&lt;/p&gt;
 &lt;p&gt;Let&amp;apos;s recall the phone trap example from the beginning:&lt;/p&gt;
 &lt;p&gt;One evening after dinner, you lie on the sofa with the mentality of &amp;quot;just scrolling for a bit,&amp;quot; casually opening a short video app:&lt;/p&gt;
 &lt;p&gt;Just as analyzed earlier, after finishing each video, you face the microscopic choice of &amp;quot;watching the next video&amp;quot; versus &amp;quot;putting down the phone&amp;quot; again. But unfortunately, the relationship $I(\text{short video}) \gg I(\text{put down phone})$ holds true for every microscopic node, so you can&amp;apos;t put down the phone at any node.&lt;/p&gt;
 &lt;p&gt;Suppose based on all your past choice records, we statistically calculate the probability of you ultimately taking either of the two branches, you&amp;apos;ll find the difference between the two is extremely disparate - perhaps as high as 99% versus 1% (this is just a conceptual example, actual statistics aren&amp;apos;t needed).&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Inescapable zone diagram" src="https://pic2.zhimg.com/v2-d34e6a417ac5e72381b0d246d85e8f03_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Now, we can introduce a key assumption -   &lt;strong&gt;the &amp;quot;Limited Free Will&amp;quot; assumption&lt;/strong&gt;:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;Free will exists, but it is also limited.&lt;/strong&gt; The smaller the tendency difference between options, the higher the probability that free will can effectively intervene. If it&amp;apos;s a 60%:40% difference, free will can still intervene; but when the difference between options is too large, such as reaching 99%:1%, free will becomes almost powerless.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;In fact, the moment you lie on the sofa and open a short video app, your behavioral state is like a fighter jet locked by missile radar, trapped in an &amp;quot;inescapable zone&amp;quot; that&amp;apos;s difficult to escape from. Within this zone, you cannot break free from inside merely through the tiny struggles of free will, and   &lt;strong&gt;can only sit and wait for destruction in a statistical sense.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Until the anxiety of staying up late slowly grows, the numbness from scrolling slowly develops, and the tendency difference between the two options slowly narrows to a range where free will can intervene, only then can you successfully change out of this state.&lt;/p&gt;
 &lt;p&gt;In other words, when you chose to pick up your phone and lie down on the sofa at that moment,   &lt;strong&gt;you had actually already determined the next several hours of wasting away in a larger-scale sense.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Based on the above observations, we can also propose a further definition:&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;When a string of decision nodes has a probability difference exceeding a certain threshold (such as 90%:10%), we can consider it beyond the intervention range of free will. Then, directly ignore the option with lower probability and coarse-grain these microscopic nodes into an overall &amp;quot;inescapable zone.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="Coarse-graining of inescapable zones" src="https://pica.zhimg.com/v2-c2e390b1d645e3d51ce6ec792f767d50_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;If we look from a larger scale, within these &amp;quot;inescapable zones,&amp;quot; those seemingly independent decision nodes have already been determined in a statistical sense by larger-scale factors (such as immediate temptation, current emotions, physical fatigue, and deeply rooted habits). Small-scale free will choices, such as whether to scroll through videos or play games, which specific short video to watch, become unimportant instead.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;RSIP Recursive Stabilization Iteration Protocol&lt;/h2&gt;
 &lt;h3&gt;14&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;This phenomenon where the importance of various influencing factors waxes and wanes at different scales is not limited to the self-control domain, but widely exists in various complex systems.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;In statistical physics, there is an elegant and profound theory called   &lt;strong&gt;Renormalization Group Theory&lt;/strong&gt;, which describes precisely this phenomenon. In 1966, American physicist Leo Kadanoff proposed such an idea:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;When you change the scale of observation, the internal degrees of freedom of the system will continuously merge (coarse-grain), the system&amp;apos;s macroscopic behavior will gradually be dominated by a few key variables, while a large number of microscopic variables gradually become unimportant.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;To simply understand this method, imagine such a small game:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Lattice model for renormalization" src="https://pic2.zhimg.com/v2-1c5fc956b14216660ad0b1a557639115_1440w.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Before you is a very large lattice chessboard, with an arrow on each square, pointing either up (↑) or down (↓). We can design some rules to influence the arrows&amp;apos; directions, for example:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;We can add a rule to make each arrow tend to be consistent with its neighbors;&lt;/li&gt;
  &lt;li&gt;Or conversely, make arrows tend to oppose their neighbors;&lt;/li&gt;
  &lt;li&gt;We can also add local noise and disturbances;&lt;/li&gt;
  &lt;li&gt;And furthermore, sometimes we can apply overall external intervention;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;Under these densely interwoven local rules, many tiny disturbances, even a tiny change in one square, might spread to the surroundings, forming complex chain reactions.&lt;/strong&gt; You might think that to analyze the overall pattern, you&amp;apos;d need to count every single arrow clearly.&lt;/p&gt;
 &lt;p&gt;But Kadanoff said:   &lt;strong&gt;No need for such complexity. As long as we&amp;apos;re willing to be a bit &amp;quot;blurry,&amp;quot; to see a bit more coarsely, patterns will automatically emerge.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;He designed an ingenious &amp;quot;coarse-graining&amp;quot; game rule:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Merge adjacent 2×2 small squares into one large square;&lt;/li&gt;
  &lt;li&gt;Use a &amp;quot;winner takes all&amp;quot; approach to decide this large square&amp;apos;s direction (for example, three ↑ and one ↓, then the whole block is marked as ↑. If the numbers are equal, randomly choose a direction, or handle according to some simple rule);&lt;/li&gt;
  &lt;li&gt;Then, continue repeating this merging operation with the newly obtained large squares...&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="Coarse-graining process" src="https://pica.zhimg.com/v2-b654cd670db4596b315b1bba60a5b8ca_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;After round after round of &amp;quot;merging&amp;quot; coarse-graining, our perspective pulls farther and farther back, becoming increasingly blurry. The arrow diagram that initially looked chaotically detailed in its details ends up with just a few large regions, perhaps with most directions tending toward consistency.&lt;/p&gt;
 &lt;p&gt;What happened in this process?&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;First, those local rules with small scope of action, each doing their own thing, will be submerged during merging, or cancel each other out on average, ultimately completely disappearing at large scales. The structures thus produced therefore gradually fade away;&lt;/li&gt;
  &lt;li&gt;Conversely, those trends with wider scope and stronger consistency can survive in each layer of coarse-graining, gradually becoming prominent at larger scales;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Thus, this coarse-graining not only changes the lattice, it can also change the strength of the &amp;quot;rules&amp;quot; themselves! Whether a rule is important depends on which scale you observe it at.&lt;/strong&gt; Factors that are crucial at the microscopic scale may have no trace at large scales; while those seemingly weak but consistent trends may instead become the system&amp;apos;s ultimate dominant force.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Let&amp;apos;s consider a simple example.&lt;/p&gt;
 &lt;p&gt;Suppose on this chessboard, there are two types of forces competing with each other:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;One is    &lt;strong&gt;short-range exchange interaction&lt;/strong&gt;, very strong, but only affects nearest neighbor squares, making adjacent arrows tend toward consistency;&lt;/li&gt;
  &lt;li&gt;The other is    &lt;strong&gt;long-range dipolar interaction&lt;/strong&gt;, relatively weak, but can act over longer distances, attempting to make arrows in regions point in opposite directions.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;(Note: In the real world there might also be complex factors like anisotropy, thermal fluctuations, defects, stress, etc. This is just a very simplified hypothetical discussion)&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Competition between interactions" src="https://pica.zhimg.com/v2-767825b68a8d87806e15ad80a5ace488_1440w.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Without external intervention, these two forces begin a tug-of-war:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Exchange interaction tends to pull all local squares into one direction, forming numerous locally homogeneous small regions;&lt;/li&gt;
  &lt;li&gt;Dipolar interaction won&amp;apos;t allow the entire system to point in the same direction for too long; it tries to &amp;quot;pull&amp;quot; these regions from afar, promoting alternating directions between them, forming mutually canceling arrangements;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Ultimately, under the   &lt;strong&gt;competition and compromise&lt;/strong&gt; of these two forces, a delicate balance is reached: You&amp;apos;ll see large patches of arrows automatically cluster, forming &amp;quot;islands&amp;quot; of varying sizes and alternating directions. Each island is internally uniform in direction, while different islands differ from each other. These islands nestle against each other, ultimately stabilizing into a complex yet stable &amp;quot;puzzle&amp;quot; structure.&lt;/p&gt;
 &lt;p&gt;In the real world, this phenomenon is called   &lt;strong&gt;magnetic domains&lt;/strong&gt;, which is precisely the result of the classic short-range exchange - long-range dipolar competition model.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;15&lt;/h3&gt;
 &lt;p&gt;So, when we observe this chessboard at different scales, what happens?&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;At the microscopic scale, we mainly see patches of highly uniform local regions:&lt;/strong&gt; At this point, exchange interaction is very powerful, rapidly making surrounding squares&amp;apos; directions tend toward consistency; while dipolar action, being too weak, is almost imperceptible at this microscopic perspective.&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;But when we coarse-grain to larger scales, what we see are large blocks of different directions alternating with each other:&lt;/strong&gt; At this point, although exchange interaction is strong, its range of action is too short, only able to influence internal small-scale unifying trends.    &lt;strong&gt;Its voice doesn&amp;apos;t grow with coarse-graining&lt;/strong&gt;; conversely, dipolar interaction has sufficient range - at large scales, its weak action continuously adds up, slowly accumulating, gradually occupying a dominant position, forming obvious macroscopic structure.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;This is the most profound point revealed by renormalization group thinking.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The importance of a factor often depends on the observation scale you&amp;apos;re at - rules that are crucial at small scales may be completely averaged out at larger scales; while those long-range trends that are almost imperceptible at small scales may accumulate layer by layer during coarse-graining, ultimately dominating the system&amp;apos;s macroscopic direction.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Scale-dependent importance" src="https://picx.zhimg.com/v2-3217cfd3d8d0d60974b968af184b9c4b_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;This is one of the most universal laws of the entire physical world:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;A glass of water is countless molecules in complex and chaotic collisions microscopically, but macroscopically can be completely described with just a few simple variables: temperature, pressure, and volume;&lt;/li&gt;
  &lt;li&gt;A magnet at the atomic scale has chaotically disordered magnetic moments, but at the macroscopic scale condenses clear north and south poles;&lt;/li&gt;
  &lt;li&gt;Every atom of a spring follows complex quantum mechanics, but from the outside, macroscopically it&amp;apos;s just a simple Hooke&amp;apos;s law $F = kx$;&lt;/li&gt;
  &lt;li&gt;In financial markets, small-period trends no matter how complexly they fluctuate will ultimately follow the script set by large-period trends.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;As the observation scale continuously enlarges, most local fluctuations, short-range volatility, and microscopic rules will automatically be merged, be offset, be absorbed into other variables.   &lt;strong&gt;And the system&amp;apos;s ultimate macroscopic behavior only depends on very few variables with wide range, strong consistency, that can continuously accumulate and survive at larger scales.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;(Note: These two sections are only metaphorical analogies to life, not rigorous reasoning)&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;16&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;What&amp;apos;s shocking is that such renormalization thinking can actually be extended to life itself.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Now, let&amp;apos;s also establish a rule for our life timeline like the &amp;quot;winner takes all&amp;quot; rule for merging small squares: When the probability difference between various behavioral options exceeds a certain threshold (such as 90%:10%) within a period of time, we consider free will unable to intervene, then merge this time period, coarse-graining it into an &amp;quot;inescapable zone.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— You&amp;apos;ll find that most of our life is composed of large and small &amp;quot;inescapable zones,&amp;quot; pieced together like magnetic domains!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;When you&amp;apos;re lying in bed late at night scrolling through short videos, it&amp;apos;s a negative &amp;quot;inescapable zone&amp;quot; - you probably won&amp;apos;t suddenly put down your phone midway;&lt;/p&gt;
 &lt;p&gt;When you&amp;apos;re eating, it&amp;apos;s still an inescapable zone - you probably won&amp;apos;t suddenly stop eating and go to the library to study;&lt;/p&gt;
 &lt;p&gt;When you&amp;apos;re studying, it&amp;apos;s a positive inescapable zone - after entering the state, you won&amp;apos;t easily stop without encountering resistance.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Or perhaps you might say: &amp;quot;Wait, don&amp;apos;t we always have some moments where we still have room for choice?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;But unfortunately, when pulled to larger scales, those surface-level freedoms are caged under even larger &amp;quot;inescapable zones&amp;quot;:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;If you only slept three hours last night, your script is probably wasted for the whole day. Even if you force yourself to start studying, it&amp;apos;s difficult to persist. You&amp;apos;ll be trapped in the cycle of being tired on one side while alternating between short videos and games on the other, relying on the high stimulation of phones to maintain wakefulness. This can be coarse-grained into a day-level &amp;quot;inescapable zone&amp;quot;;&lt;/li&gt;
  &lt;li&gt;If this week you have completely no deadlines, reversed schedule, stuck at home with nothing to do, then your behavior will be trapped in the death cycle of continuous fatigue → decadence → reversed schedule → even more fatigue. This is another week-level &amp;quot;inescapable zone&amp;quot;;&lt;/li&gt;
  &lt;li&gt;As the final exam deadline approaches, your script this month is often: initially procrastinating and decaying → then easily entering study mode a few days before the exam, ultimately regretting why you didn&amp;apos;t start earlier. Looking back, you find that at every semester&amp;apos;s end, you&amp;apos;re arranged crystal clear by this month-level &amp;quot;inescapable zone&amp;quot; script.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="Nested inescapable zones" src="https://pic2.zhimg.com/v2-c84e00a3997348c03ed6a4502bd94549_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;My friend, these layer upon layer nested scripts, cycles, and inescapable zones like Russian nesting dolls are the truth of our lives.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;How many ways can we spend one minute? Perhaps hundreds or thousands: you might be scrolling through short video A, or scrolling through short video B, might be reading, or might be doing a certain problem.&lt;/p&gt;
 &lt;p&gt;But if we pull the time scale slightly larger, our one hour might only have a few dozen ways to spend it; might just be scrolling through short videos / studying / commuting / eating;&lt;/p&gt;
 &lt;p&gt;By one day, there might only be a dozen or so ways; one month, perhaps only seven or eight typical patterns; pulling to a one-year scale, you might be shocked to find that our year actually only has three or four rough scripts left.&lt;/p&gt;
 &lt;p&gt;As our perspective pulls back, progressively coarse-graining the entire event chain, the influence of those small-scale details becomes smaller and smaller, gradually averaged out. The entire system is increasingly dominated by those corresponding large-scale factors, such as environment, work, habits, personality, etc. Small-scale variables control small-scale scripts, large-scale variables control large-scale scripts:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Your attention state controls your thoughts every second;&lt;/li&gt;
  &lt;li&gt;The thing currently being done pulls your attention every minute;&lt;/li&gt;
  &lt;li&gt;Today&amp;apos;s schedule arrangement determines what you&amp;apos;re specifically doing each hour;&lt;/li&gt;
  &lt;li&gt;The energy and emotional state of recent days affects each day&amp;apos;s specific arrangements;&lt;/li&gt;
  &lt;li&gt;Going further up, your sleep routine, emotional cycles, environmental conditions, even your home layout, control your recent overall life state;&lt;/li&gt;
  &lt;li&gt;And your identity, personality, and social situation determine your long-term rhythm, ultimately shaping the pattern of your entire life.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;At each scale, once the corresponding factors are given, they become a solid &amp;quot;boundary condition,&amp;quot; causing the system to spontaneously form several   &lt;strong&gt;behavioral patterns that are easiest to stably exist&lt;/strong&gt; at the current scale, as the current system&amp;apos;s   &lt;strong&gt;&amp;quot;stable states.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;When the boundary condition of &amp;quot;only slept three hours last night&amp;quot; is determined, alternating indulgence between scrolling through videos and playing games in these two small stable states is the behavioral pattern easiest to stably exist within the day. At this scale, a few minutes of attention, one hour of matters, are already unimportant;&lt;/li&gt;
  &lt;li&gt;When the boundary conditions of &amp;quot;no recent deadlines,&amp;quot; &amp;quot;reversed schedule,&amp;quot; &amp;quot;stuck at home&amp;quot; are determined, all-day decadence cycling through late nights, fatigue, and indulgence among several small stable states is the behavioral pattern easiest to stably exist within the week. At this scale, one afternoon&amp;apos;s energy, one or two days&amp;apos; schedules, are already unimportant;&lt;/li&gt;
  &lt;li&gt;When the boundary conditions like &amp;quot;exam time,&amp;quot; &amp;quot;personal habits,&amp;quot; &amp;quot;external environment&amp;quot; are determined, the early-stage procrastination decadence small stable state + late-stage cramming small stable state is again this month&amp;apos;s behavioral pattern easiest to stably exist. At this point, several days&amp;apos; state, one week&amp;apos;s motivation, are already unimportant.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;In this multi-scale large nesting doll, looking down, each stable state is composed of several smaller-scale stable states; looking up, several such stable states alternate and repeat, further combining to form larger stable state structures.&lt;/p&gt;
 &lt;p&gt;These stable states are highly stable, highly repetitive, and highly dependent on those key factors at corresponding scales. It&amp;apos;s precisely these large and small influencing factors and stable states, interwoven together, that jointly constitute the &amp;quot;ecological environment&amp;quot; of our daily lives.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;From this perspective, &amp;quot;free will&amp;quot; at the bottom layer is truly life&amp;apos;s greatest illusion.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;We possess free will completely every second, seem to possess free will every minute, relatively possess free will every hour, and by each day, each week, seem to no longer possess free will much. Macroscopically, we&amp;apos;re just a piece of duckweed drifting with the waves in the vast ocean composed of environment, habits, circumstances, personality, and interest relations, our bodies involuntarily.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And the essence of all human self-control strategies is actually using the smallest-scale free will, with the support of various rules, to attempt from bottom-up to overcome large-scale unfavorable factors and shift larger-scale behaviors - this is undoubtedly as difficult as ascending to heaven.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;All efforts and slogans, whether CTDP or otherwise, are merely attempting to stir up pale and powerless waves in this vast ocean. This is the tragedy we, with weak self-control, are naturally destined to encounter.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;17&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Now, we can finally look back and more clearly understand what bottlenecks local behavioral intervention strategies like CTDP will encounter.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The first bottleneck is the &amp;quot;scale limitation problem&amp;quot;: CTDP can naturally only influence local behaviors, unable to shift those long-term factors behind these behaviors.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;The core principle of CTDP is through a series of ingenious mechanisms to amplify rational tendency in the short term (such as one hour of focused tasks), reduce startup resistance, thereby greatly improving single-behavior execution. At the microscopic scale, it indeed demonstrates astonishing effectiveness.&lt;/p&gt;
 &lt;p&gt;But the problem is: Our lives are not pieced together from a string of isolated hours.   &lt;strong&gt;&amp;quot;Not willing to start a task at this moment&amp;quot; is only the surface phenomenon, while those larger-scale factors causing this &amp;quot;unwillingness to start,&amp;quot; such as energy state, emotional fluctuations, clear sense of goals, life rhythm, and even long-term habits, are the true essence.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;When these large-scale factors are unfavorable to you, even using the most ingenious strategy to force yourself to study for one hour at the moment often has very limited effect:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;If you&amp;apos;ve recently frequently stayed up late and are physically and mentally exhausted, you won&amp;apos;t even rationally want to study, and CTDP cannot solve the staying up late problem itself;&lt;/li&gt;
  &lt;li&gt;If you recently have completely no clear tasks or urgent deadlines, then CTDP similarly cannot urge you to actively self-study, after all, you&amp;apos;ve never had the habit of self-studying.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Therefore, CTDP&amp;apos;s first fatal limitation lies in   &lt;strong&gt;it can only influence microscopic nodes, but cannot shift upward those macroscopic factors that determine our life&amp;apos;s fundamental tone - it cannot make you energetic, nor can it make you mentally stable, much less shake your habits or life patterns.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The second bottleneck is the &amp;quot;stable state regression problem&amp;quot;: Even if we temporarily change our life state, the system will spontaneously regress back to the original stable patterns.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;As stated earlier, when larger-scale factors like energy state, life rhythm, mindset, and habits are determined, the system will naturally form a series of highly stable behavioral patterns. For example, at the beginning of winter vacation when just returning home, &amp;quot;playing games&amp;quot; and &amp;quot;scrolling through videos&amp;quot; might be the easiest life pattern to maintain, so your daily life will probably cycle repeatedly between &amp;quot;playing games&amp;quot; and &amp;quot;scrolling through videos.&amp;quot;&lt;/p&gt;
 &lt;p&gt;CTDP&amp;apos;s problem is that it can only temporarily intervene at certain nodes of the system.   &lt;strong&gt;Even if it forcibly replaces certain hours with efficient study mode, so what?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The system&amp;apos;s stable state is still that original stable state! Pulling the time scale longer, these few hours of efficiency will still be submerged in large chunks of playing games and scrolling through videos.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;For those larger-scale (day-level, week-level) stable states determined by habits, sleep, and emotional states, these few hours of efficiency can&amp;apos;t even stir up a wave, let alone change the stable state itself.&lt;/p&gt;
 &lt;p&gt;This spontaneous regression mechanism is precisely the fundamental reason for the frequent occurrence of &amp;quot;rebound,&amp;quot; &amp;quot;returning to original state,&amp;quot; and &amp;quot;intermittent efficiency&amp;quot; phenomena.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The third bottleneck, also the most fundamental and desperate, is the &amp;quot;binding force dissipation problem&amp;quot;: Any attempt to deviate from the stable state is consuming resources (such as willpower) to maintain a metastable state, which necessarily cannot last long.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;When a person&amp;apos;s behavioral system forms some stable structure at large scales, it&amp;apos;s like a self-perpetuating ecosystem, possessing natural &amp;quot;stable state attraction.&amp;quot; Any effort to break free from this stable state essentially means needing to continuously invest various resources:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Perhaps your precious willpower;&lt;/li&gt;
  &lt;li&gt;Perhaps the sunk cost ingeniously designed by CTDP;&lt;/li&gt;
  &lt;li&gt;Perhaps various binding rules, check-ins, supervision;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;But the problem is:   &lt;strong&gt;These resources are finite&lt;/strong&gt;. When you try to maintain a &amp;quot;metastable state&amp;quot; that deviates from the original stable state, even if initially very successful, it will gradually wear away and exhaust under continuous negative factors, ultimately binding force collapses and regresses back to the original state. And aggressively changing the entire state all at once requires resources and strength that are difficult to maintain.&lt;/p&gt;
 &lt;p&gt;Is it possible for there to exist a &amp;quot;next better stable state&amp;quot; above the current stable state?&lt;/p&gt;
 &lt;p&gt;There indeed might be. Sometimes, even when large-scale factors are unfavorable, you might coincidentally be efficient for several consecutive days. But unfortunately, this state is often attainable but not sustainable. Before long, your life returns to its original state.&lt;/p&gt;
 &lt;p&gt;The most tragic thing is:   &lt;strong&gt;Almost all self-control methods lack the power to change the entire stable state in a long-term, holistic, and immediate manner.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;These self-control methods only use finite resources to support those bit of local binding rules, unable to shift the whole. When the shift fails, these strategies fall into the dilemma of &amp;quot;whack-a-mole&amp;quot;: having constrained studying on this side, life order becomes chaotic on the other; having regularized sleep schedule on this side, emotional state collapses on that side.&lt;/p&gt;
 &lt;p&gt;This is because a large negative stable state is often interwoven from multiple small negative stable states: staying up late causes complete lack of energy, lack of energy makes it easier to indulge in games, indulging in games makes you chase stimulation, chasing stimulation makes you stay up later. This comprehensive negative state is like a Lernaean Hydra - strike its head and the tail arrives, strike its tail and the head arrives, strike its middle and both head and tail arrive.&lt;/p&gt;
 &lt;p&gt;Ultimately, you must admit a cruel reality:&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Under the premise of finite resources, all efforts attempting to break free from the current base stable state almost face the same fate - brief success followed by rapid regression, unable to achieve a true stable state transition.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;In summary, these three bottlenecks together constitute CTDP&amp;apos;s theoretical limits that are difficult to transcend. To fundamentally break through these limitations, we must have a brand new, more globally-minded second-generation method.&lt;/p&gt;
 &lt;p&gt;And several years later, I finally found the key that can truly solve these problems.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;18&lt;/h3&gt;
 &lt;p&gt;It was a rainy night. At the time, I had just received my PhD offer but was then stuck by visa issues. Feeling helpless, I clicked on a xiangqi (Chinese chess) commentary video. It was about the famous &amp;quot;Lone Horse Slays King&amp;quot; game from the 1960 Sichuan chess players&amp;apos; visit to Wuhan performance match: Li Yiting versus Chen Deyuan.&lt;/p&gt;
 &lt;p&gt;At the end, the commentator mentioned:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;The game has now reached a &amp;quot;checkmate in three moves&amp;quot; position.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="Xiangqi checkmate position" src="https://pic3.zhimg.com/v2-71b5a1e5c3e88003a41247ebb5693776_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;What does so-called &amp;quot;checkmate in three moves&amp;quot; mean?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;It means that after entering this position, Black is actually already inevitably doomed. No matter how it responds, Red can always checkmate Black within three rounds. All of Black&amp;apos;s struggles can only delay the time of being checkmated, and if the struggle is improper, it might even hasten this time.&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Xiangqi game tree" src="https://pic2.zhimg.com/v2-0e23089b64d5969f5ff6af5de1da9819_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;So why did Black fall into such a dire situation?&lt;/p&gt;
 &lt;p&gt;Of course it&amp;apos;s because its last move was wrong. If Black could take back the move and return to the previous step, perhaps it could avoid falling into the &amp;quot;checkmate in three moves&amp;quot; death trap; if returning to the previous step is still a &amp;quot;checkmate in four moves&amp;quot; and still cannot avoid being checkmated, then the move before that was already wrong. And so on.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;You&amp;apos;ll find that if we keep backtracking, we can necessarily backtrack to some move that would allow Black&amp;apos;s hope of winning to reappear&lt;/strong&gt; (at least within the computer&amp;apos;s search range, Red cannot find a winning method).&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And this point is precisely the key to breaking the deadlock for the second-generation method.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Backtracking in chess" src="https://pic3.zhimg.com/v2-0e286216ab0650168af5a8ea3a5ef8d6_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Back to the example of lying on the sofa playing with your phone at the beginning.&lt;/p&gt;
 &lt;p&gt;When lying on the sofa scrolling through videos, we&amp;apos;ve actually already unavoidably entered a death cycle. But what if we backtrack forward?&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;To avoid starting to scroll through videos (99%:1%), we must avoid being on the sofa with phone in hand (80%:20%);&lt;/li&gt;
  &lt;li&gt;To avoid being on the sofa with phone in hand, we must avoid bringing the phone onto the sofa (60%:40%);&lt;/li&gt;
  &lt;li&gt;We can continue: to avoid bringing the phone onto the sofa, we must avoid entering a state where it&amp;apos;s easy to bring the phone onto the sofa (50%:50%)...&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Often in this series of event backtracking,   &lt;strong&gt;the earlier you backtrack to, the smaller the tendency difference between the two options becomes!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Thus, we finally discovered an exciting law:&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;For any seemingly inescapable &amp;quot;inescapable zone,&amp;quot; we can necessarily backtrack to some node - at this node, the tendency difference between the two options is small enough, entering the effective intervention range of free will, allowing us to truly avoid entering that final negative death trap.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;— That is to say, every seemingly powerful, large-scale negative stable state can be mapped to a weak, small-scale effective intervention node!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And this node is where the true boundary of the &amp;quot;inescapable zone&amp;quot; lies, and also the only chance for small-scale will to resist large-scale unfavorable factors.&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;19&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Seeing this, some people might say: Isn&amp;apos;t this something everyone can think of? You&amp;apos;ve talked about so much profound theory, renormalization groups, behavioral stable states, inescapable zones, and so on. In the end, what you&amp;apos;re saying is just that same old thing about not bringing your phone onto the sofa.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;This kind of thing has been talked to death already. Today this blogger tells you not bringing your phone to bed can change your life; tomorrow that chicken soup tells you life turnaround starts from small things; the principle of staying away from temptation and preventing problems before they occur was discovered thousands of years ago. You use a pile of physics theories just to say this - isn&amp;apos;t this using a cannon to shoot mosquitoes, making things unnecessarily mysterious?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;But my friend, please don&amp;apos;t be hasty. This is just our first building block.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;As stated earlier, the types of negative states we face in life are actually extremely limited and highly repetitive; and for any given negative state, we can always find an effective intervention node through backtracking.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;If we precisely apply constraints to it, we can achieve a &amp;quot;move a thousand pounds with four ounces&amp;quot; effect, preventing problems before they occur with minimal cost, avoiding entering those negative states from the very beginning. This constraint is the local optimal solution for this situation.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;There are many such small technical optimal solutions. Let me give a few examples:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;For example, &amp;quot;never bringing your phone into the bedroom from the start&amp;quot; is much easier than &amp;quot;bringing the phone into the bedroom but remembering not to scroll on it.&amp;quot; Then    &lt;strong&gt;never bringing your phone into the bedroom from the start&lt;/strong&gt; is naturally the optimal solution to solve the &amp;quot;scrolling phone before bed&amp;quot; problem;&lt;/li&gt;
  &lt;li&gt;For example, &amp;quot;immediately taking a shower after arriving home and having nothing to do&amp;quot; is much easier than &amp;quot;after already lying on the sofa playing with phone, forcing yourself to get up and shower.&amp;quot; Then having your phone automatically set a 15-minute countdown after detecting you&amp;apos;ve arrived home, during which you must start showering, is the optimal solution to solve &amp;quot;shower procrastination&amp;quot;;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;Of course, besides these simple and direct strategies everyone can think of, it can be extended much further:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;You can make certain behaviors simpler: For example, &amp;quot;no playing phone after 11 PM&amp;quot; can help you sleep early, but achieving it isn&amp;apos;t easy; then, why not add &amp;quot;after 10:30 PM only allowed to play phone while standing&amp;quot; as an auxiliary. After all, going from playing phone to not playing phone is difficult, but going from playing phone to standing while playing phone, and from standing while playing phone to not playing phone, are both easier;&lt;/li&gt;
  &lt;li&gt;You can also improve the overall state: For example, you might know that what you do after getting up each day can often set the tone for the whole day. Then not playing phone for 30 minutes after getting up each day, only doing proper things, can greatly improve the state for the entire day;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;(Where does the binding force of these regulations come from? This will be explained later)&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And most interestingly: Since negative states themselves are repetitive, then the &amp;quot;local optimal solutions&amp;quot; targeting these states are naturally equally repetitive!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;In games like xiangqi and Go, such optimal operational plans targeting certain specific situations that can be repeatedly applied are called &amp;quot;joseki&amp;quot; (定式，fixed patterns).&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;So-called joseki are actually local optimal solutions summarized by countless predecessors after deep research: under specific local situations, both black and white players must strictly follow the joseki; if either side deviates from the script, they&amp;apos;ll inevitably leave vulnerabilities.&lt;/p&gt;
 &lt;p&gt;Even in the ever-changing chess games, players can greatly improve their overall chess skill by mastering individual local joseki.&lt;/p&gt;
 &lt;p&gt;And we can also learn joseki like chess players, using a &amp;quot;divide and conquer algorithm&amp;quot; logic to break down negative states in life one by one:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;First, we can identify typical negative problems in life;&lt;/li&gt;
  &lt;li&gt;Each negative problem can be further decomposed into several negative stable states;&lt;/li&gt;
  &lt;li&gt;And each negative stable state can be mapped to corresponding effective intervention nodes through backtracking;&lt;/li&gt;
  &lt;li&gt;Finally, for each intervention node, we can custom-design a precise    &lt;strong&gt;&amp;quot;joseki&amp;quot; to break it&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;This way, we can precisely concentrate finite self-control resources on those truly most critical nodes, performing reduced-scale strikes on those highly repetitive negative stable states one by one.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;If four ounces can move a thousand pounds, then why not use eight ounces to move two thousand pounds, twelve ounces to move three thousand pounds, sixteen ounces to move four thousand pounds?&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;20&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Even more wonderful: eight ounces might move more than just two thousand pounds.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Suppose we&amp;apos;ve truly found a local joseki and are willing to invest certain resources (such as method design, willpower investment, or external constraints) to implement it, successfully completely &amp;quot;banning&amp;quot; some negative state from life - for example, never again lying on the sofa with phone, or always immediately showering after arriving home.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Then, because its scale of action is long enough, this &amp;quot;joseki&amp;quot; itself will also become a large-scale factor, becoming one of the numerous boundary conditions influencing life&amp;apos;s stable state!&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;At this point, is the stable state still that original stable state?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Obviously not. If we record the initial stable state as $E_0$, then when the first joseki is successfully introduced, your life will gradually enter a slightly improved metastable state $E_1$. In the new stable state $E_1$, because the overall state has improved by just that little bit, the difficulty of introducing the second joseki will decrease somewhat.&lt;/p&gt;
 &lt;p&gt;Similarly, after adding the second joseki, you enter the more optimized metastable state $E_2$. Introducing the third joseki in $E_2$ becomes even easier.&lt;/p&gt;
 &lt;p&gt;For example, if you achieve &amp;quot;not playing phone on the sofa,&amp;quot; implementing &amp;quot;immediately shower after arriving home&amp;quot; will also be slightly easier; if you immediately shower after arriving home daily achieving a refreshed state, implementing &amp;quot;don&amp;apos;t scroll phone before bed&amp;quot; will also be a bit easier because you&amp;apos;ve solved shower procrastination.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Each new joseki introduced might produce a &amp;quot;1+1&amp;gt;2&amp;quot; effect, ultimately in a &amp;quot;slicing sausage&amp;quot; manner, gradually improving your life, step by step transitioning toward a better long-term stable state. It&amp;apos;s also possible that after you&amp;apos;ve added a dozen or so joseki, some difficult problem seemingly unrelated to them will unexpectedly naturally disappear after improvement in energy, habits, and other large-scale conditions.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;As the   &lt;em&gt;Art of War&lt;/em&gt; states: &amp;quot;One skilled in warfare wins victories that are easily won.&amp;quot;&lt;/p&gt;
 &lt;p&gt;After solving simple problems, originally medium-difficulty problems become simple problems; if we conquer this new simple problem, then originally difficult problems also become simple problems.&lt;/p&gt;
 &lt;p&gt;From beginning to end, we&amp;apos;re solving simple problems.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Alright, we&amp;apos;ve found a series of joseki, each able to avoid entering a negative &amp;quot;inescapable zone&amp;quot; from the very beginning. But to thoroughly achieve large-scale stable state transition, we ultimately must still face that most fundamental challenge - the binding force limitation problem.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;As analyzed earlier, all self-control strategies essentially use finite resources to support some local binding rules. This unavoidably leads to the &amp;quot;whack-a-mole&amp;quot; dilemma: just talked beautifully about going from $E_0$ to $E_1$, $E_1$ to $E_2$,   &lt;strong&gt;but in reality, what might happen is you simply don&amp;apos;t have this much energy to maintain these joseki.&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;When you try to add a new joseki, some old joseki might suddenly collapse;&lt;/li&gt;
  &lt;li&gt;Or you encourage growth by pulling up shoots, forcibly mounting a joseki with overly high requirements at the start. Before it can improve the overall state, maintaining it exhausts all resources;&lt;/li&gt;
  &lt;li&gt;Or you introduce a new joseki incompatible with existing joseki, resulting in the entire system instantly crashing.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;On one hand, adding new joseki requires investing more maintenance costs; but on the other hand, it can slightly improve the existing stable state, and over time will form habits, gradually reducing maintenance costs. Thus, the problem transforms into a competition between the speed of stable state improvement and the speed of maintenance cost increase.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Therefore, the sequence of introducing joseki itself is also crucial. Not just any arbitrary order can support you in reaching the next stable state. Additionally, where does the binding force of so many joseki come from?&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;To thoroughly solve this problem, we need to introduce a new ingenious algorithm - the &amp;quot;Recursive Backtracking Algorithm.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;21&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;The so-called Recursive Backtracking Algorithm is actually a classic algorithmic thinking widely used in computer science.&lt;/strong&gt; It&amp;apos;s typically used to solve such problems: when you face a system with extremely huge possibilities (such as navigating a maze, playing chess), how to find a feasible, or even optimal path under the premise of finite resources.&lt;/p&gt;
 &lt;p&gt;The most typical example is the   &lt;strong&gt;maze problem&lt;/strong&gt;:&lt;/p&gt;
 &lt;p&gt;Imagine you&amp;apos;re trapped in a maze. You don&amp;apos;t know which path leads to the exit, and you have no map. All you can do is continuously try every walkable path:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Each time you encounter a fork, you first randomly choose a path;&lt;/li&gt;
  &lt;li&gt;If you walk down and discover this path doesn&amp;apos;t work (dead end), you    &lt;strong&gt;retreat&lt;/strong&gt; to the previous fork and try    &lt;strong&gt;another&lt;/strong&gt; possible path;&lt;/li&gt;
  &lt;li&gt;If the new path also doesn&amp;apos;t work, you continue retreating, changing direction again, until you finally find a path leading to the exit.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;This is the core logic of the &amp;quot;Recursive Backtracking Algorithm&amp;quot;: Try → Fail → Withdraw → Change path → Continue trying, until success. Like in chess, continuously taking back moves to ensure finding a winning path.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Recursive backtracking visualization" src="https://pic2.zhimg.com/v2-5168794de70ae0e8dccded416cd764dd_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;So how should we apply the recursive backtracking algorithm to design the addition sequence of these &amp;quot;joseki&amp;quot;?&lt;/p&gt;
 &lt;p&gt;Suppose we&amp;apos;ve designed a series of joseki targeting various negative states in life, such as:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Joseki (A) ensuring you always shower when arriving home;&lt;/li&gt;
  &lt;li&gt;Joseki (B) ensuring you don&amp;apos;t bring phone onto sofa;&lt;/li&gt;
  &lt;li&gt;Joseki (C) ensuring you don&amp;apos;t scroll Xiaohongshu at night;&lt;/li&gt;
  &lt;li&gt;Joseki (D) ensuring you wash dishes promptly after meals;&lt;/li&gt;
  &lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;At this point, we can use this &amp;quot;  &lt;strong&gt;Joseki Tree&lt;/strong&gt;&amp;quot; approach to organize and manage them:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;strong&gt;Joseki addition rules: If on a given day you successfully meet the requirements of a certain joseki, you can add it as a child node to the joseki tree, but add at most one per day.&lt;/strong&gt; For example, if I find joseki H is highly related to existing joseki F, then I can add joseki H as a child node of F; if I find joseki E seems like an entirely new domain, I can also directly establish a new branch.&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;Joseki deletion rules: Manage with a &amp;quot;stack structure.&amp;quot; Once a joseki is deleted, simultaneously delete all its child nodes.&lt;/strong&gt; For example, if joseki C has one failed execution, this indicates that the combination of joseki C and subsequent F, H is unstable, so generously delete it, simultaneously deleting subsequent F, H joseki. (Of course, in the future you can try adding joseki C again to the tree&amp;apos;s end)&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="Joseki tree structure" src="https://picx.zhimg.com/v2-6c819767b171a26b990cffc9eb4a1549_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Through such repeated iteration, we naturally solve both the &amp;quot;joseki addition sequence&amp;quot; search problem and the &amp;quot;binding force source&amp;quot; problem:&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;On one hand, joseki added more &amp;quot;naturally&amp;quot; and easier to maintain are more likely to stably remain at the root of this tree.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The reason is: in this iteration process, if a certain joseki&amp;apos;s maintenance cost is too high and cannot be stably maintained under current conditions, it will naturally collapse and retreat back to the tree&amp;apos;s end; conversely, those high-quality joseki that are added easily, maintained without burden, and significantly improve overall state can more easily be retained at the tree&amp;apos;s root.&lt;/p&gt;
 &lt;p&gt;Over time, the nodes closest to the front of this joseki tree are instead all those seemingly trivial small rules that can bring huge positive effects. Can you guess what my current #1 root node joseki is? It&amp;apos;s actually just &amp;quot;must wash dishes promptly after eating at home.&amp;quot; Though it sounds unremarkable, it can truly avoid falling into a larger decadent state.&lt;/p&gt;
 &lt;p&gt;It&amp;apos;s precisely these simple and tiny improvements accumulating at the root that make the entire joseki tree&amp;apos;s foundation increasingly solid. Like &amp;quot;passive buffs&amp;quot; in games, continuously providing tiny improvements to the large state, supporting the entire tree to steadily advance step by step. This is the true &amp;quot;winning victories that are easily won.&amp;quot;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;On the other hand, this stack structure will also naturally provide powerful binding force for newly added joseki.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Think about it: since introducing each joseki requires one day&amp;apos;s effort, then if a joseki with four child nodes suddenly fails, this means your entire five days of effort, entire five joseki effective for self-control instantly wasted - and this loss occurs the instant you prepare to give up.&lt;/p&gt;
 &lt;p&gt;This is its second wonderful aspect: the closer to the root, the more child nodes a joseki has, the greater the loss cost, therefore the better it&amp;apos;s protected.&lt;/p&gt;
 &lt;p&gt;Until finally, those joseki deeply buried at the root will gradually internalize into your habits because execution time has been long, requiring less and less resources to maintain, ultimately almost negligible. At this point, the constraint power saved can be invested in developing new joseki.&lt;/p&gt;
 &lt;p&gt;And this method is called   &lt;strong&gt;Recursive Stabilization Iteration Protocol (RSIP).&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;It answers a widely existing question:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;Under the condition of finite resources, how to govern a complex system&amp;apos;s comprehensive negative stable state?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;The answer is, we can target several small negative stable states through targeted backtracking, finding corresponding local optimal solutions as &amp;quot;joseki&amp;quot;; then, integrate them into a tree-structure diagram, using recursive backtracking search algorithm to explore the addition sequence of these joseki. Once a joseki cannot be maintained, decisively and recursively abandon it and its child nodes in the tree. Ultimately, only the path most beneficial to the entire system&amp;apos;s stability can be retained - just as Qian Xuesen&amp;apos;s ideological proposition advocated: &amp;quot;Not seeking advancement of individual technologies, only seeking overall design rationality.&amp;quot;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Just like this, we finally achieved the miracle of self-control strategies - integrating numerous local optimal solutions into the joseki tree, accumulated, small-scale free will can be amplified to a magnitude sufficient to influence the global situation.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;And from here, you can truly begin transforming sleep schedule, cultivating energy, improving health, adjusting life rhythm, eliminating habits you want to eliminate, cultivating habits you want to cultivate, making those previously seemingly immovable large-scale factors start working for you.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;22&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Of course, in practical application, if you want to gamify RSIP, it&amp;apos;s also quite easy.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Because in reality, this method already has an almost identical counterpart - that&amp;apos;s the famous   &lt;strong&gt;National Focus Tree&lt;/strong&gt; in the strategy game   &lt;em&gt;Hearts of Iron&lt;/em&gt; series:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Hearts of Iron National Focus Tree" src="https://pic4.zhimg.com/v2-71df943a293646fc1211c7509f0e5deb_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;In this game, each country has a huge &amp;quot;national focus tree,&amp;quot; with the tree full of various diverse national focus nodes:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Some are used for industrial expansion;&lt;/li&gt;
  &lt;li&gt;Some strengthen military construction;&lt;/li&gt;
  &lt;li&gt;Some determine diplomatic direction;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Each national focus can provide players with various bonus effects, and choosing and designing one&amp;apos;s own country&amp;apos;s national focus tree is one of this game&amp;apos;s greatest charms.&lt;/p&gt;
 &lt;p&gt;In practical application, I also like using mind mapping software (such as MindMaster) to manage such national focus trees. The process of designing various national focuses for RSIP is actually quite interesting:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Personal National Focus Tree example" src="https://pica.zhimg.com/v2-1324af1b924dac20306bbd772346d80a_r.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;For example, targeting the evening shower procrastination problem, the &amp;quot;national focus&amp;quot; I designed is:    &lt;strong&gt;Using iPhone&amp;apos;s automation function, once evening positioning detects returning home, automatically start a 15-minute countdown, must enter bathroom to start showering before countdown ends;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;For another example, to address the problem of playing with phone after waking up in the morning causing the whole day to be muddled, the &amp;quot;national focus&amp;quot; I designed is:    &lt;strong&gt;Strictly prohibit using phone for the first 30 minutes after waking, can only do proper things like washing up, organizing, eating breakfast, or checking emails, thereby activating the day&amp;apos;s state;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;And to ensure the RSIP system itself can continuously operate stably, I also specially designed a &amp;quot;root national focus&amp;quot; at the very root:    &lt;strong&gt;During the morning wake-up period, must open the national focus tree mind map page, and must add one new national focus every day.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Meanwhile, if you want to make it more fun, national focuses can also be &amp;quot;strengthened and upgraded&amp;quot; like game equipment.&lt;/p&gt;
 &lt;p&gt;For example, a progressible national focus called &amp;quot;Sleep Reserve Plan&amp;quot; can be arranged under the early sleep/early rise national focus, progressively advancing phone curfew/wake time - if one day you successfully achieve 22:45/7:45, record it as &amp;quot;Enhancement +1&amp;quot;; and on another day achieve 22:40/7:40, can upgrade to &amp;quot;Enhancement +2.&amp;quot;&lt;/p&gt;
 &lt;p&gt;This upgradeable national focus can protect its base node through stored redundancy. Because even if one time you have to stay up late due to circumstances, you can release it, return to the +0 state, and your habitual sleep rhythm won&amp;apos;t suddenly collapse (leading to its child nodes also being difficult to maintain).&lt;/p&gt;
 &lt;p&gt;In fact, the vast majority of current online discussions about self-control are actually unsystematic, extremely fragmentary &amp;quot;local suggestions.&amp;quot; Today this marketing account tells you locking up your phone can keep you away from it; tomorrow that chicken soup blogger makes a whole video series just telling one old cliché about breaking down goals. They all vehemently claim that after listening to their methodology, your life will be overturned and changed. Unfortunately in reality, most of these experiences are just life hacks at best.&lt;/p&gt;
 &lt;p&gt;However, I don&amp;apos;t completely oppose these experiences, but rather provide a framework to design these locally useful techniques into individual &amp;quot;national focuses,&amp;quot; incorporating them into this system.&lt;/p&gt;
 &lt;p&gt;Future applications are yours to freely explore.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;Afterword&lt;/h2&gt;
 &lt;h3&gt;23&lt;/h3&gt;
 &lt;p&gt;The genesis of this article stemmed from my accidentally seeing a note about ADHD by @Allvinn on Xiaohongshu. At the time, I was just touched by my own years of bitter struggle with ADHD and self-control problems, and casually wrote some comments, unexpectedly giving birth to the idea of organizing these accumulated thoughts from over the years into writing.&lt;/p&gt;
 &lt;p&gt;Honestly speaking, I&amp;apos;m not any particularly excellent person. Just a very ordinary student who grew up in parents&amp;apos; doting and addiction to games from childhood, long plagued by terrible habits and severe self-control problems. Throughout my past life, my grades also often struggled repeatedly between rock bottom and top ranks. Fortunately, I stumbled along all the way, also getting into graduate and doctoral programs, becoming an ordinary scientific research brick mover, able to quietly do things I enjoy in one small laboratory.&lt;/p&gt;
 &lt;p&gt;In fact, most people have more or less thought about things related to &amp;quot;self-discipline&amp;quot; since enlightenment, and I&amp;apos;m just someone who happened to sustain this thinking a bit longer.&lt;/p&gt;
 &lt;p&gt;However, at the end of the article, I actually want to convey a message more:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;I am extremely sensitive to the excessive deification and promotion of &amp;quot;self-discipline&amp;quot; and &amp;quot;methodology&amp;quot; by many marketing accounts and knowledge bloggers now. As if just learning their methodology, attending their training camps, one becomes self-disciplined, hardworking, defying fate changed, starting to counterattack.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;I don&amp;apos;t think so.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;In fact, self-control methods have never been any magic pill to make life cheat-mode. Even if you achieve absolute self-discipline, so what? This is still just one piece among countless puzzle pieces in your life, and not necessarily the most important piece.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;In life, there are many puzzle pieces more important than this - physical and mental health, family environment, social resources, personal personality, interpersonal relationships, furthermore the elusive luck, even the era you&amp;apos;re in itself - they can all like fate&amp;apos;s stones, like nails on Newton&amp;apos;s board, push a young person&amp;apos;s industrious and self-disciplined efforts toward unknown distances.&lt;/p&gt;
 &lt;p&gt;Our era indeed has quite a few people suffering from lack of self-discipline; but there are also many suffering from excessive self-discipline, from internal pressure and obsession with &amp;quot;excellence.&amp;quot;   &lt;strong&gt;Self-discipline has never been something everyone needs; what many people need more is relaxation, healthily and freely doing things they enjoy.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Therefore, this article&amp;apos;s efforts are actually just attempting to help some people, to some degree, solve some problems.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Not everyone needs self-discipline;&lt;/p&gt;
 &lt;p&gt;Not everyone who needs self-discipline is suitable for the methods proposed in this article;&lt;/p&gt;
 &lt;p&gt;Not everyone suitable can understand it;&lt;/p&gt;
 &lt;p&gt;Much less can everyone who understands it truly benefit from it.&lt;/p&gt;
 &lt;p&gt;But I myself have genuinely experienced, in the past decade or so, the regret, missed opportunities, internal consumption and sense of powerlessness caused by lack of self-discipline.&lt;/p&gt;
 &lt;p&gt;In the vast sea of humanity, might there be a second me? If there truly is such a person, I&amp;apos;m willing to hold up a small umbrella for him/her.&lt;/p&gt;
 &lt;p&gt;Even if this umbrella can only help one person similar to my former self among every thousand people in the world, then all of this would be worth it enough.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;24&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;Having spoken about individuals, let&amp;apos;s talk about society.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;To this day, society actually already has a certain degree of understanding and tolerance for depression. But in comparison, understanding and tolerance for &amp;quot;lack of self-control,&amp;quot; &amp;quot;procrastination,&amp;quot; &amp;quot;ADHD&amp;quot; and other problems is still far from enough.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Now, for depression patients, more and more enlightened people are beginning to realize this is a genuine psychological illness, not simply &amp;quot;矫情&amp;quot; (being pretentious) or &amp;quot;can&amp;apos;t think it through.&amp;quot; They&amp;apos;re starting to try understanding the real pain patients face from attention collapse, loss of interest, sleep disorders, emotional dullness, and even somatization.&lt;/p&gt;
 &lt;p&gt;However, toward ADHD patients and those with long-term self-control deficiency, people&amp;apos;s attitudes are often still crude, harsh, lacking empathy:&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Their procrastination, laziness, difficulty taking action are often mocked as &amp;quot;lazy people finding excuses&amp;quot;;&lt;/li&gt;
  &lt;li&gt;Their falling into patterns of attention diffusion and easy addiction is seen as &amp;quot;poor self-control&amp;quot; and &amp;quot;can&amp;apos;t control themselves&amp;quot;;&lt;/li&gt;
  &lt;li&gt;Even many ADHD patients who struggle upward using various means, while regretting their loss of control, often get cold sarcastic remarks like &amp;quot;self-touching,&amp;quot; &amp;quot;浮气躁&amp;quot; (impetuous), &amp;quot;just pretending to work hard,&amp;quot; &amp;quot;suitable for factory work.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;In such a cultural atmosphere, people refuse to respect the objective fact that &amp;quot;human self-control has limitations.&amp;quot; Many people are unwilling to admit that human subjective agency is not infinite, that it&amp;apos;s limited by neural structure, hormone levels, psychological state, external environment, long-term habits and many other objective factors.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;quot;Lack of self-control&amp;quot; has never been an attitude or mindset problem that can be solved through motivation and persuasion, but rather an objectively existing engineering problem, systems problem, even medical problem.&lt;/p&gt;
 &lt;p&gt;Attempting to use slogans, chicken soup, whip-and-carrot motivational traditional psychology preaching to solve such problems is like using &amp;quot;you&amp;apos;re too fragile,&amp;quot; &amp;quot;be more optimistic,&amp;quot; &amp;quot;just think it through and you&amp;apos;ll be fine&amp;quot; to comfort a depression patient - equally laughable.&lt;/p&gt;
 &lt;p&gt;I really like that line from   &lt;em&gt;The Great Gatsby&lt;/em&gt;:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;&amp;quot;Whenever you feel like criticizing anyone, just remember that all the people in this world haven&amp;apos;t had the advantages that you&amp;apos;ve had.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;Just as human sorrows and joys are not mutually intelligible, in the matter of self-discipline, human conditions in all aspects are also not mutually intelligible.&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Some people cultivated good habit foundations from childhood, in an environment full of progressive atmosphere. They might only need to make a plan list, smile in the mirror a few times each day to easily achieve Easy-difficulty self-discipline;&lt;/li&gt;
  &lt;li&gt;But some people have been addicted to phones since childhood, with lazy habits and decadent family environment. On this foundation, achieving self-discipline might be Hard-difficulty hell.&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Self-control, as something we&amp;apos;ve commonly faced since childhood, has been more or less thought about by almost everyone. So if all these people have thought about &amp;quot;how to self-control,&amp;quot; what would be the result?&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;This forms society&amp;apos;s general cognition of &amp;quot;self-discipline&amp;quot; - either stopping after fifty steps, or stopping after a hundred steps.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;The essence of self-control methods is just a prosthesis for self-control deficiency. Just like crutches and wheelchairs are prostheses for disabled people&amp;apos;s motor abilities. Healthy people don&amp;apos;t need crutches and wheelchairs, and patients whose injuries have healed also no longer need crutches and wheelchairs.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Once this gap is filled, people no longer pursue more advanced crutches and wheelchairs. So the answer becomes &amp;quot;the pony crossing the river&amp;quot;: Easy-difficulty people will say smiling in the mirror works; Medium-difficulty people will suggest you put down your phone and make plans; Hard-difficulty people think you must rely on external supervision, even live-streaming while studying.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;And an extremely counterintuitive social phenomenon precisely comes from this.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;In personal achievement, self-discipline is actually just one of many factors. Those Easy-difficulty players, besides being easier to achieve self-discipline, also possess better habit foundations, environmental help, and social resources, thus also more easily achieving success. Therefore, if you observe those who achieved success, you&amp;apos;ll find they&amp;apos;re probably precisely from &amp;quot;Easy difficulty.&amp;quot; In their eyes, self-discipline is as easy as doing push-ups in an elevator.&lt;/p&gt;
 &lt;p&gt;This creates a strange survivorship bias -   &lt;strong&gt;the more successful people are, the more likely they are to use ineffective self-discipline methods.&lt;/strong&gt; Just as healthier people increasingly lack real experience using crutches and wheelchairs.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Even more terrible is when society establishes these Easy-mode players as role models for everyone, granting them supreme discourse power, it forms an extremely cruel, &amp;quot;why not eat meat porridge&amp;quot; atmosphere toward those truly lacking self-control.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;I don&amp;apos;t know how many times I&amp;apos;ve scrolled past comments from so-called &amp;quot;people who&amp;apos;ve been there,&amp;quot; who sneer at self-control methods. In their mouths, &amp;quot;back in our day we just had one path up Mount Hua, just do it and it&amp;apos;s done, just work hard and it&amp;apos;s done. Where&amp;apos;s all this beating around the bush?&amp;quot;&lt;/p&gt;
 &lt;p&gt;There was also once when I scrolled past an interview video with a gaokao (college entrance exam) top scorer. When the host asked about &amp;quot;how to view many students being decadent, depressed, lacking motivation, unable to self-discipline,&amp;quot; that top scorer paused slightly, raised a proud yet tender face, and expressed sincerely and confusedly:&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;&amp;quot;To be honest, I really can&amp;apos;t quite understand why some people would have no motivation.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;Perhaps in their world, self-discipline is as simple as breathing and walking, motivation as natural as the daily sun. So they perhaps aren&amp;apos;t acting with malice, just purely unable to understand.&lt;/p&gt;
 &lt;p&gt;More often, we can also see many marketing accounts and self-media bloggers with bright shiny titles sharing so-called &amp;quot;self-discipline experiences.&amp;quot; Clicking in to look, the whole article talks about &amp;quot;because of love,&amp;quot; &amp;quot;because of high energy&amp;quot; - this is still normal. More bloggers are instead giving classes, opening their mouths about &amp;quot;thought models,&amp;quot; closing their mouths about &amp;quot;cognitive upgrades,&amp;quot; talking about Dao, talking about Zen, talking about energy sublimation, talking about spiritual healing.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;I&amp;apos;m not criticizing those who have smooth sailing - this isn&amp;apos;t their fault.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;I&amp;apos;m just trying to point out a fact - human experiences are not naturally mutually intelligible; human subjective effort also can never exist independently from objective conditions.&lt;/p&gt;
 &lt;p&gt;Of course, we can&amp;apos;t expect everyone to understand complex psychological mechanisms and behavioral science. We can&amp;apos;t even expect everyone to be sufficiently kind.&lt;/p&gt;
 &lt;p&gt;But I&amp;apos;m willing, beyond those who walked fifty or a hundred steps, as an ADHD patient with extremely poor self-control foundation, to embark from the lowest starting point on that thousand-step long journey. So much so that behind these two generations of methods are hundreds of failed ideas, hundreds and thousands of methodologies that have appeared on the current internet and in related books - I&amp;apos;ve thought about, tried, and analyzed every one. After this, I&amp;apos;ve finally reached the endpoint - finally possessing a normal person&amp;apos;s self-discipline level.&lt;/p&gt;
 &lt;p&gt;Perhaps this path only suits me alone. But dear stranger, if it can help even one person, even just you, then I also feel all of this is worth enough.&lt;/p&gt;
 &lt;p&gt;I hope it can, amidst the currently overwhelming &amp;quot;put down your phone,&amp;quot; &amp;quot;make plans,&amp;quot; imagine the future, &amp;quot;tell yourself&amp;quot; type permutation-combination superficial &amp;quot;advice,&amp;quot; walk a completely different path belonging to &amp;quot;technology.&amp;quot;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;&amp;quot;Cavalry drenched in sweat and blood, attack speed equivalent to 20th century armored divisions; Northern Song&amp;apos;s bed crossbows, firing range reaching fifteen hundred meters, comparable to 20th century sniper rifles; but these are still just ancient cavalry and bows, impossible to contend with modern forces. Basic theory decides everything - the Futurist school clearly saw this point.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;This is the truest reason I wrote this article.&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h3&gt;25&lt;/h3&gt;
 &lt;p&gt;Finally, let me say: the author will not use this content itself for any profit, will not establish any paid communities, and doesn&amp;apos;t need follows and likes.   &lt;strong&gt;Money can be earned and also spent, follows can gather and also dissipate, but thought and technology are always there.&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;If this article can fortunately benefit some people, my only wish is not your likes and returns, but rather hoping more people can have one more portion of respect and empathy toward those whose conditions, habits, achievements, and education are inferior to their own, and I&amp;apos;ll be satisfied.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Finally, please let me imitate an MIT License as the ending:&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;Anyone has the right to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the content in this article for free and without restriction, and allow this content to be used for commercial purposes without needing to obtain permission from the author or pay any fees - only needing to credit the original author. All income generated from adapting this article into videos, comics, promotional articles, etc. belongs to the adapter; this article&amp;apos;s author takes no share. Additionally, all tips, gifts and other income will be entirely used to promote this content itself. However, if the author personally contributes labor and participates in commercialization projects based on this article (such as planning, cooperative development, etc.), then related rights and interests shall be separately agreed upon according to normal commercial standards.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;The purpose of this article is precisely what I mentioned at the end of&lt;/strong&gt;   &lt;a href="https://www.zhihu.com/question/22164041/answer/148128347" rel="noopener noreferrer" target="_blank"&gt;this answer&lt;/a&gt;   &lt;strong&gt;- Proletarians of the world, unite!&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>post</category>
      <guid isPermaLink="true">https://itindex.net/detail/63080-translation-of-%E4%B8%AD%E6%96%87</guid>
      <pubDate>Wed, 05 Nov 2025 03:12:04 CST</pubDate>
    </item>
    <item>
      <title>分享一个平时工作找需求找竞品的方法：通过产品使用的核心技术库 - 即刻App</title>
      <link>https://itindex.net/detail/62946-%E5%88%86%E4%BA%AB-%E5%B7%A5%E4%BD%9C-%E9%9C%80%E6%B1%82</link>
      <description>&lt;div&gt;分享一个平时工作找需求找竞品的方法：通过产品使用的核心技术库，找使用这个库的 project showcase。    &lt;br /&gt;    &lt;br /&gt;起源是公司内部最近出现了一个我和 leader 都觉得惊为天人的产品。但是因为是保密项目我们能获取到的信息不够，我们判断这种交互模式和 UI 体验是借鉴了某个海外产品，大概率不是我司的原创。但是因为这个产品是很垂类的 B 端场景，加上保密项目的原因内部获取不到什么技术方案和竞品调研。    &lt;br /&gt;    &lt;br /&gt;灵机一动发现这个产品的核心技术库是 xy-flow（一种画布容器解决方案），于是便从 xy-flow 的官网找到了市面上使用 xy-flow 的 project showcase。    &lt;br /&gt;    &lt;br /&gt;从 project showcase 中，我看到了太多太多以前从未想到、从未接触的垂类场景：    &lt;br /&gt;    &lt;br /&gt;- JSON 与工作流打通的 JSONSea    &lt;br /&gt;- 数据监控与工作流画布结合的 Redata    &lt;br /&gt;- 客服与弹窗 rpa 工作流的 Botfront    &lt;br /&gt;- 与地理数据结合的物流系统 Geoplex    &lt;br /&gt;- 包括我们自己的竞争对手 retool 和 wix 的 workflow    &lt;br /&gt;- …    &lt;br /&gt;    &lt;br /&gt;这些都在使用 xy-flow 构建工作流、画布容器。今天几分钟的时间，从这一个基础库我看到了许多从未接触过的业务场景，这些业务场景背后代表的是一定数量的用户群体以及潜在的同类型竞品。代表着某个行业、某个市场。    &lt;br /&gt;    &lt;br /&gt;这种查找需求的方法之前    &lt;a href="https://m.okjike.com/users/a2d6acc1-626f-4d15-a22a-849e88a4c9f0"&gt;@哥飞&lt;/a&gt;飞哥也在社群和公众号分享过类似的，并且可操作性更强范围更广：通过产品的入口流量、出口流量挖掘需求，以及榜单、外链。具体的操作和知识可以去「哥飞」公众号上刷一遍学习一下。    &lt;br /&gt;    &lt;br /&gt;习得了某个方法某个知识后，举一反三在生活工作场景中用出来、总结出来，那种感觉真的是不一样的。    &lt;br /&gt;    &lt;br /&gt;总得来说，这种找需求的方法考察的是多角度多维地看待问题。直接看是现象，从上游看是解决方案，从下游看是业务场景。    &lt;br /&gt;    &lt;br /&gt;    &lt;a href="https://reactflow.dev/showcase"&gt;reactflow.dev&lt;/a&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62946-%E5%88%86%E4%BA%AB-%E5%B7%A5%E4%BD%9C-%E9%9C%80%E6%B1%82</guid>
      <pubDate>Tue, 15 Oct 2024 10:40:49 CST</pubDate>
    </item>
    <item>
      <title>数据中心网络高可用技术之从交换机到交换机：MLAG, 堆叠技术</title>
      <link>https://itindex.net/detail/62923-%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%83-%E7%BD%91%E7%BB%9C-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;在  &lt;a href="https://www.kawabangga.com/?p=6509"&gt;上一篇文章结束对链路聚合的讨论&lt;/a&gt;之后，我们发现一个问题——我们只能用多条线连接到同一个 switch 上面，这样万一这个交换机挂了，连接这个这个交换机的机器（通常是一个 Rack）就一起消失了。&lt;/p&gt;



 &lt;h2&gt;MLAG 技术&lt;/h2&gt;



 &lt;p&gt;MLAG(Multi-Chassis Link Aggregation) 可以提供跨设备的链路聚合功能。&lt;/p&gt;


 &lt;div&gt;
  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2024/08/cisco-modular-switch.jpeg"&gt;   &lt;img alt="" height="289" src="https://www.kawabangga.com/wp-content/uploads/2024/08/cisco-modular-switch.jpeg" width="605"&gt;&lt;/img&gt;&lt;/a&gt;思科模块化交换机&lt;/div&gt;


 &lt;p&gt;Multi-Chassis 这个词很有意思，为什么叫做 Chassis （机箱）而不叫座 Multi-Switch LAG 呢？因为这个功能不仅仅是运行在 Switch 上，还记得在  &lt;a href="https://www.kawabangga.com/posts/6295"&gt;理解网络的分层模型&lt;/a&gt;中说的吗？二层和三层设备的界限已经越来越模糊了，三层设备也可以有这个功能。&lt;/p&gt;



 &lt;p&gt;现在的网络设备都是模块化的，一个机箱上面可以根据自己的需求插不同的线卡，卡上面甚至还能装不同的模块，满足不同的需求。机箱可以认为是网络设备单元，以「机箱」来说，意思就是运行于不同网络设备之间的功能。&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;MLAG 是一个设备的 feature，而不是协议，所以在不同厂商的产品中，MLAG，Peer Link 等术语会有所不同。&lt;/em&gt;&lt;/p&gt;


 &lt;div&gt;
  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2024/08/mlag.png"&gt;   &lt;img alt="" height="306" src="https://www.kawabangga.com/wp-content/uploads/2024/08/mlag.png" width="424"&gt;&lt;/img&gt;&lt;/a&gt;多交换机 MLAG&lt;/div&gt;


 &lt;p&gt;对于客户端 Linux 来说，不知道对端是两个设备，在 Linux 的视角下，自己的多条线路连接的就是同一个设备。&lt;/p&gt;



 &lt;p&gt;在交换机侧，就需要跨设备完成 LACP 信息的同步，两个交换机设备之间需要协调好，A 的 3号端口 和 B 的 3号端口是同一个链路聚合组(LAG)。所以两个交换机之间需要一条线，来沟通控制面的信息。这条线就叫做 Peer Link。在 Peer Link 上如何传输控制信息，取决于不同厂商对于 MLAG 的实现。某些厂商的设备使用   &lt;a href="https://en.wikipedia.org/wiki/IEC_60870-6"&gt;ICCP&lt;/a&gt; (Inter-Control Center Communications Protocol) 来进行不同的机箱之间的连接。&lt;/p&gt;



 &lt;p&gt;这样，就可以完成从服务器到交换机的高可用了，服务器网卡、网线、交换机接口、交换机系统、交换机整机，任意一个地方出现问题，都有冗余路线，不会对服务造成太大影响。&lt;/p&gt;



 &lt;p&gt;数据中心广泛使用的就是 MLAG。&lt;/p&gt;



 &lt;h2&gt;堆叠技术&lt;/h2&gt;



 &lt;p&gt;在交换机之间的高可用，还有一种技术，就是交换机堆叠。这个功能在不同的厂商里也是不同的名字，比如华为的 istack，思科的 StackWise.&lt;/p&gt;



 &lt;p&gt;简单来说，这个功能可以让多个交换机虚拟成一个。只有一个主操作系统在运行，其他的交换机就像主交换机扩展出来的板卡一样。堆叠之后只有一个管理 IP 和 MAC 地址，只需要登录一个系统进行配置操作。&lt;/p&gt;



 &lt;a href="https://www.kawabangga.com/wp-content/uploads/2024/08/ip-mac-stacking-switch.png"&gt;  &lt;img alt="" height="430" src="https://www.kawabangga.com/wp-content/uploads/2024/08/ip-mac-stacking-switch-1024x430.png" width="1024"&gt;&lt;/img&gt;&lt;/a&gt;4个交换机完成堆叠之后，相当于一个交换机有了 4 倍的端口



 &lt;p&gt;堆叠之后逻辑上就是一个交换机，所以服务器可以直接连接到多个物理交换机，从逻辑上看，交换机侧也是同一个了。&lt;/p&gt;



 &lt;a href="https://www.kawabangga.com/wp-content/uploads/2024/08/switch-stacking.png"&gt;  &lt;img alt="" height="629" src="https://www.kawabangga.com/wp-content/uploads/2024/08/switch-stacking-1024x629.png" width="1024"&gt;&lt;/img&gt;&lt;/a&gt;交换机堆叠连接服务器



 &lt;p&gt;堆叠也能实现故障快速切换，在正常情况下也能充分利用线路的带宽，配置简单。但是和 MLAG 相比，稳定性上来说 MLAG 更高，因为堆叠交换机只有一个控制面，如果主交换机出现故障，比如堆叠失效，整个堆叠集群都会出错。MLAG 的故障域更小，交换机坏也就坏一台。这也带来很多维护便利，从升级维护上说，MLAG 可以让我们一台一台地操作交换机升级而不影响服务，堆叠就会更加麻烦一些。从部署上，MLAG 不受距离显示，堆叠的话，两个交换机距离越远，出错的概率越大。&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;说起来，思科还有一个    &lt;a href="https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus7000/sw/vdc/config/cisco_nexus7000_vdc_config_guide_8x/overview.html"&gt;VDC 技术&lt;/a&gt;(Virtual Device Context), 支持把一个设备虚拟成多个。这些技术又是把一个物理设备拆成多个又是把多个合成一个的，可真有意思。&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;这个系列的二层技术介绍的差不多了，我们下一篇就开始聊三层技术。&lt;/p&gt;



 &lt;p&gt;Until next time!&lt;/p&gt;



 &lt;h2&gt;数据中心网络高可用技术系列&lt;/h2&gt;



 &lt;ol&gt;
  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6417"&gt;数据中心网络高可用技术：序&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6430"&gt;数据中心网络高可用技术之从服务器到交换机：active-backup&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6485"&gt;数据中心网络高可用技术之从服务器到交换机：balance-tlb 和 balance-alb&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6460"&gt;数据中心网络高可用技术之从服务器到交换机：链路聚合 (balance-xor, balance-rr, broadcast)&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6509"&gt;数据中心网络高可用技术之从服务器到交换机：802.3 ad&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6537"&gt;数据中心网络高可用技术之从交换机到交换机：MLAG, 堆叠技术&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://www.kawabangga.com/posts/6594"&gt;数据中心网络高可用技术之从服务器到网关：VRRP&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>网络 ICCP (Inter-Control Center Communications Protocol) MLAG (Multi-Chassis Link Aggregation) Peer Link VDC (Virtual Device Context)</category>
      <guid isPermaLink="true">https://itindex.net/detail/62923-%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%83-%E7%BD%91%E7%BB%9C-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Thu, 08 Aug 2024 10:04:25 CST</pubDate>
    </item>
    <item>
      <title>流动的爱？约会软件技术可供性如何影响用户的择偶实践</title>
      <link>https://itindex.net/detail/62872-%E7%BA%A6%E4%BC%9A-%E8%BD%AF%E4%BB%B6-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;h6&gt;作者：安美星&lt;/h6&gt; &lt;h6&gt;来源：微信公众号：羊村传播（ID：yangcunmedia）&lt;/h6&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;近年来，主打陌生人交友的各类社交平台在青年人群体中风靡，替代朋友、同学、亲戚等传统熟人社交成为其拓展社会交往范围、寻求亲密关系的重要渠道。高成本的线下社交、难以突破的固定圈层以及来自社会期望的压力等，使那些有婚恋需求的人们寄希望于各种约会软件，试图将其作为寻找自己“另一半”的渠道。拥有地理定位、学历身份认证以及算法配对功能的约会软件确实让部分用户得偿所愿，收获了从认识到相爱再到相伴的圆满爱情。然而，随着约会软件合法化并嵌入到社会文化肌理，其作用也不再局限于拓展社会网络，而是潜移默化地改变着人们的择偶实践。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h1&gt;  &lt;strong&gt;01&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;  &lt;strong&gt;   &lt;strong&gt;追溯：“罗曼蒂克”的兴起与数字化时代的爱情&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;尽管西方贵族有“罗曼蒂克（romantic）”的爱情，在中国也不乏才子佳人的故事，但“父母之命，媒妁之言”才是传统社会婚恋关系的真实写照。&lt;/strong&gt;一直到19世纪现代化社会来临之后，“个人化”自由选择配偶“婚姻私人化”才开始兴起，由此才有真正意义上的“浪漫的爱”。吉登斯认为现代社会对于平等和自我的强调将“性”从繁殖中解放出来，人们越来越追求一种平等的基于双方情感和满足的“纯粹的关系”。对于纯粹浪漫关系的追求也使人们将关系中的亲密性和幸福感凌驾于社会文化准则之上，这种充满偶然性和不确定性的关系逐渐变得难以维持(Hobbs et al.，2017)。面对这些变化，  &lt;strong&gt;学者们指出当代人的“后现代”爱情观一方面重视情感的交流与表达，追求轰轰烈烈的爱情，另一方面又觉得基于激情的婚姻不够扎实&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;进入数字化时代，在线约会软件的出现与流行成为学者们关注的焦点。围绕约会软件是否会影响人们的择偶实践和亲密关系这个问题，人们展开了一系列相关研究，包括用户关系忠诚度(Alexopoulos et al.，2020）、择偶倾向（Barrada et al.，2021)、形象展演(Hanson, K. R.，2021)以及约会实践(Solis et al.，2019)等。有研究显示，约会软件的使用会对人们的亲密关系产生负面的影响，使不少关系陷入危机，比如缺乏沟通、降低关系忠诚度(Alexopoulos et al., 2020）、引发信任危机、引起不必要的攀比等问题频现(Rosenfeld，2018)。但同时这也让人们意识到线下关系的重要性，特别是当关系出现危机的时候，面对面的沟通可能让双方准确地意识到问题所在并得到有效解决。此外，约会平台的算法逻辑及其隐含的政策使后现代的浪漫关系具有乌托邦特性：一种无风险的、无痛苦、高效率的互动，剥夺了浪漫关系的复杂性(Bandinell, C.，2022)。有研究者表示约会软件的“滑动”逻辑会破坏亲密关系的发展(David et al.，2016)。综上，约会软件流行不仅仅在亲密关系中充当渠道和媒介，而是嵌入亲密关系实践并影响着现代人的择偶观。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h1&gt;  &lt;strong&gt;02&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;  &lt;strong&gt;   &lt;strong&gt;解读：技术可供性视角下的约会软件对择偶实践的影响&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;技术可供性强调行动者意图与技术性能的相互关系，指某一特定背景下行动者感知到的其能够使用媒介展开行动（与其需求与或目标有关）的潜能与媒介潜在特性、能力、约束范围的关系。在技术可供性的视角下，技术、社会与人之间不是简单的技术决定社会或者社会决定技术的二元互构关系，而是人与技术之间的一种功能性平衡。技术可供性理论启发我们不再孤立的研究技术，而是强调技术、环境与行动者等多方面的关系结构决定了用户在数字环境中潜在行为和结果。约会平台的技术架构赋予其可编辑性、连接性、个性化以及社交性等可供性，这些技术特性能够为用户所感知并据此展开各种行为，最终塑造了数字时代的择偶实践。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;连接性与社交性降低人们对于关系的忠诚度。&lt;/strong&gt;约会软件通过技术将移动端的用户紧密联系，相较于传统的社群和组织，无论是质量还是数量上，约会软件都为用户提供了更多的选择。与此同时，约会软件基于地理位置的接近性来为用户推送匹配对象逻辑，方便人们将虚拟空间中的互动对象纳入现实择偶的范围(Yeo &amp;amp; Fung，2016)。不少用户利用平台的连接性“在与一个人约会的同时与两三个人维持关系” (Hobbs et al.，2017)。多样化的选择和便捷的沟通方式为用户提供了选择的余地，让其对失去有恃无恐，也难以心甘情愿地投入到长期关系的维持中。有研究显示，  &lt;strong&gt;在线约会平台中感知到有更多的可潜在对象和约会成功率会正向影响用户对关系的不忠倾向(Alexopoulos et al.，2020)&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;个性化强化择偶的功利化倾向。&lt;/strong&gt;个体化时代的婚恋具有自由和风险并存的特征，人们一方面要忠于自己的情感和兴趣，另一方面也要尽可能挑选合适的伴侣，否则就要为自己的选择负责，承担失败的风险。这种情感和经济的双重逻辑使个体面临着选择的困境。约会平台的算法技术通过协同过滤识别具有相似特征的用户并推测用户喜好，根据用户在其他社交平台的关系网络和数字痕迹来为其推送所谓的“最优选择”对象，从而缩小用户的选择范围，降低选择成本。这看似为用户提供了一种自主选择和控制的感觉，在一定程度上解决个体完全凭借自己感觉做选择的困境，却在一定程度上择偶的功利化倾向。  &lt;strong&gt;在这个以算法为中介、数据驱动的婚恋市场中，个体被算法和社会标准评估，外貌、职业、经济状况等成为衡量一个人的标准，理性和效率被应用于爱情和性的复杂性。&lt;/strong&gt;在线约会平台变成婚恋关系市场化文化中一个工具，那些看似正确的选择不过是基于物质条件的综合考量，而非个人品行气质的独特魅力，传统那种偶然的相遇变成了一种“购买和处理商品”的行为。(Bandinelli &amp;amp; Gandini，2022)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;可编辑性增加了择偶的不确定性。&lt;/strong&gt;为了弥补在线社交沟通线索缺失的特点，约会软件推出文字、图片、视频、表情包等语言线索来使用户互动更加真实具体，这也为用户进行自我形象展演提供了空间。由于身体的不在场，以及时空关系的非对称性，在线软件上的“我们”更多的是营造的“我们”，个体基于某种社交动机会自主迎合社会标准，对自我形象进行塑造，而非真实的我的直接反映。通过挑选表现较好的照片、表达自己独特的喜好和生活方式等来吸引目标对象关注、获得算法推荐成为用户彼此间心照不宣的手段。  &lt;strong&gt;用户的“人设”与真实情况出现或大或小差别，这放大了交友过程中的不确定性。&lt;/strong&gt;在这种不确定性的驱动下，用户将交友平台比作危险的“黑暗丛林”，在使用中保持谨慎(孙萍 et al., 2023)。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h1&gt;  &lt;strong&gt;03&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;  &lt;strong&gt;反思：从技术可供性视角看见“真实”的约会软件&lt;/strong&gt;  &lt;strong&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;任何媒介技术都有其偏向性，在技术可供性的支持下，在约会软件上，人们从认知到建立浪漫关系的过程被极大地缩短，同时放弃一段关系的成本逐渐减少，纯粹且长久的情感关系难以维持。此外，约会软件的技术效率逻辑与商业的功利逻辑相互耦合，并进一步作用于平台用户的择偶实践。约会软件建构了一个流行的、碎片化的虚拟社交空间，在发挥积极效用的同时，也带来了一些负面影响。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;便捷性和高效率的互动模式，促使用户陷入择偶认知陷阱&lt;/strong&gt;。在快节奏和高压力的生活方式下，人们很难有充足的精力和时间来深入了解不同的人。在线约会软件通过大数据等技术提供了用户的基本信息，包括职业、薪资、学历、外貌形象、择偶标准、兴趣爱好、生活方式等，方便用户快速了解对方。这种高效率还体现在用户能够仅仅凭借双方之间的相互点赞等简单互动就展开聊天，其匿名性的特征又避免了害羞和胆怯使对话更加开放和直白，这些技术特性使得关系的建立更加迅速高效。移动终端的普及又使用户能够利用碎片化的时间随时随地地认知了解陌生对象并展开聊天。但这种短时间高频率的互动经不起任何的考验，一旦双方发现并没有线下发展的可能便马上放弃彼此的关系(Hobbs et al., 2017)。  &lt;strong&gt;情感本应是深入的、持久的，但是由于参与的低成本和高效率，约会软件带来广阔的选择空间，参与者陷入一种“下次还会遇到更好的”“马上就可以认识新的人”的认知，陷入快速拓展人际关系、快速进入选择、快速丢弃的循环之中。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;时空异步建构“流动的空间”，催生“流动的爱”。&lt;/strong&gt;鲍曼（2003）提出由互联网中介的约会是一种“流动的爱”，他认为，虚拟关系正在逐渐取代更固定和持久的“真实”关系，媒介技术的广泛使用导致个人更多地考虑短暂的联系，而不是终身的伴侣关系。约会正在转变为一种娱乐活动，人们将其视为随时可以“按下删除键”的一次性活动(Bauman, 2013)。  &lt;strong&gt;线上约会平台通过算法技术、LBS技术等建构了一个“流动的空间”，与以熟人社交为主的微信等平台不同，在此建立起来的虚拟人际关系呈现出流动性的特征。&lt;/strong&gt;在线约会平台的互动没有约定俗成的固定社交礼仪，人与人之间的连接与断连变得十分容易。人们通常会间歇性浏览交友软件，消息的回复也没有固定时间，交友平台使用习惯呈现流动化、碎片化的特征。此外，交友平台给人们带来了“流动约会”的体验，即建立一种来去自由、并非绑定的交流关系。这样的“浅层社交”随时可以开始和结束，但又不得不在充满不确定性的关系中以“最疏离的频率”谈论最亲密的话题，因此亲密关系的建立被蒙上了一层割裂感(孙萍等，2023)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;工具理性与数字技术的结合，推动亲密关系的市场化。&lt;/strong&gt;美国社会学家乔治·瑞泽尔提出了“麦当劳化（McDonaldization）”的概念来解释现代社会运作的逻辑，麦当劳取得成功的四大理性化原则：高效率、可计算性、可预测性和全面控制，在当代社会具有广泛的应用范围(Ritzer，2021)。有学者将其应用到在线约会中，认为社交平台和软件的介入使青年的情感需求逐渐裹挟在技术和效率的追逐之中，亲密关系呈现出短期性、快餐式的样态(罗逸琳等，2022)。  &lt;strong&gt;约会软件将工具理性与数字技术相结合，通过确立可计算的衡量标准以推动恋爱过程的高效率。&lt;/strong&gt;在这种标准化氛围之下，用户逐渐用一种商品化的视角来看待自己。他们认为在婚恋市场中自己需要参与到自我品牌建构的过程中，将自己推销为“令人满意的商品”(Hobbs et al.，2017)。由此，亲密关系成了一种具有“商品化的游戏”，人们通过有选择地自我呈现来展示自己的魅力，并将其作为交换来筛选理想对象以便从中获利(Bandinelli &amp;amp; Gandini，2022)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;我们需要注意，技术特性虽然能够影响人们的婚恋方式，但并不能发挥决定性的作用。当代婚恋困境的形成更多是人们情感疏离且社会支持不足的结果，在线约会软件用户的择偶难题不仅是个体的困境，而是社会环境的反应和社会公共需求的表达。因此，除了约会软件完善技术架构、为用户搭建健康有序的交友平台之外，政府、社会组织以及个体等社会主体都要参与其中，共同解决人们的婚恋压力与生存困境。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h6&gt;参考文献：&lt;/h6&gt; &lt;h6&gt;Alexopoulos, C., Timmermans, E., &amp;amp; McNallie, J. (2020). Swiping more, committing less: Unraveling the links among dating app use, dating app success, and intention to commit infidelity.   &lt;em&gt;Computers in Human Behavior&lt;/em&gt;,  &lt;em&gt; 102&lt;/em&gt;, 172-180.&lt;/h6&gt; &lt;h6&gt;Bandinelli, C., &amp;amp; Gandini, A. (2022). Dating apps: The uncertainty of marketised love.   &lt;em&gt;Cultural Sociology&lt;/em&gt;,  &lt;em&gt; 16&lt;/em&gt;(3), 423-441.&lt;/h6&gt; &lt;h6&gt;Bandinelli, C. (2022). Dating apps: towards post-romantic love in digital societies.   &lt;em&gt;International Journal of Cultural Policy&lt;/em&gt;,   &lt;em&gt;28&lt;/em&gt;(7), 905-919.&lt;/h6&gt; &lt;h6&gt;Barrada, J. R., Castro, Á., Fernández del Río, E., &amp;amp; Ramos-Villagrasa, P. J. (2021). Do young dating app users and non-users differ in mating orientations?   &lt;em&gt;Plos One&lt;/em&gt;,  &lt;em&gt; 16&lt;/em&gt;(2), e0246350.&lt;/h6&gt; &lt;h6&gt;Bauman, Z. (2013).   &lt;em&gt;Liquid love: On the frailty of human bonds&lt;/em&gt;. New York:John Wiley &amp;amp; Sons.&lt;/h6&gt; &lt;h6&gt;Beck, U., &amp;amp; Beck-Gernsheim, E. (2018).   &lt;em&gt;The normal chaos of love&lt;/em&gt;. New York:John Wiley &amp;amp; Sons.&lt;/h6&gt; &lt;h6&gt;David, G., &amp;amp; Cambre, C. (2016). Screened intimacies: Tinder and the swipe logic.   &lt;em&gt;Social Media+ Society&lt;/em&gt;,   &lt;em&gt;2&lt;/em&gt;(2), 2056305116641976.&lt;/h6&gt; &lt;h6&gt;Hanson, K. R. (2021). Becoming a (gendered) dating app user: An analysis of how heterosexual college students navigate deception and interactional ambiguity on dating apps.   &lt;em&gt;Sexuality &amp;amp; Culture&lt;/em&gt;,  &lt;em&gt; 25&lt;/em&gt;(1), 75-92.&lt;/h6&gt; &lt;h6&gt;Hobbs, M., Owen, S., &amp;amp; Gerber, L. (2017). Liquid love? Dating apps, sex, relationships and the digital transformation of intimacy.   &lt;em&gt;Journal of Sociology&lt;/em&gt;,  &lt;em&gt; 53&lt;/em&gt;(2), 271-284.&lt;/h6&gt; &lt;h6&gt;Portolan, L., &amp;amp; McAlister, J. (2022). Jagged love: Narratives of romance on dating apps during COVID-19.   &lt;em&gt;Sexuality &amp;amp; Culture&lt;/em&gt;,  &lt;em&gt; 26&lt;/em&gt;(1), 354-372.&lt;/h6&gt; &lt;h6&gt;Ritzer, G. (2021). The McDonaldization of society.   &lt;em&gt;In the Mind&amp;apos;s Eye&lt;/em&gt; (pp. 143-152). London: Routledge.&lt;/h6&gt; &lt;h6&gt;Rosenfeld, M. (2018). Are Tinder and Dating Apps Changing Dating and Mating in the USA?.   &lt;em&gt;In: Van Hook, J., McHale, S., King, V. (eds) Families and Technology&lt;/em&gt;. National Symposium on Family Issues, vol 9. Springer, Cham.&lt;/h6&gt; &lt;h6&gt;Solis, R. J. C., &amp;amp; Wong, K. Y. J. (2019). To meet or not to meet? Measuring motivations and risks as predictors of outcomes in the use of mobile dating applications in China.  &lt;em&gt; Chinese Journal of Communication&lt;/em&gt;,   &lt;em&gt;12&lt;/em&gt;(2), 204-223.&lt;/h6&gt; &lt;h6&gt;Yeo, T. E. D., &amp;amp; Fung, T. H. (2016). Relationships form so quickly that you won&amp;apos;t cherish them: Mobile dating apps and the culture of instantaneous relationships. Proceedings of the 7th 2016 international conference on social media &amp;amp; society,&lt;/h6&gt; &lt;h6&gt;罗逸琳, 罗昊, 黄静, &amp;amp; 李晓愚. (2022). 流水线式相亲：微信相亲平台中的择偶观念与社会交往研究.   &lt;em&gt;传媒观察&lt;/em&gt;(04), 73-79.&lt;/h6&gt; &lt;h6&gt;孙萍, 李宜桐, &amp;amp; 于小童. (2023). “中介化爱情”之困：理解线上交友平台的媒介化与性别化.   &lt;em&gt;妇女研究论丛&lt;/em&gt;(01), 117-128.&lt;/h6&gt; &lt;h6&gt;  &lt;br /&gt;&lt;/h6&gt; &lt;h6&gt;作者简介：安美星，主编：曾润喜，执行主编：杨柳。本文转载自微信公众号：羊村传播（ID：yangcunmedia），新鲜有趣的新闻传播学术发现之旅。由重庆大学曾润喜老师团队运营。&lt;/h6&gt; &lt;p&gt;  &lt;br /&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/62872-%E7%BA%A6%E4%BC%9A-%E8%BD%AF%E4%BB%B6-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Wed, 25 Oct 2023 23:49:32 CST</pubDate>
    </item>
    <item>
      <title>Stable Diffusion 模型技术架构与原理</title>
      <link>https://itindex.net/detail/62827-stable-diffusion-%E6%A8%A1%E5%9E%8B</link>
      <description>&lt;p&gt;Stable Diffusion 是一个文本到图像（txt2img）的潜在扩散模型（LDM），是由 CompVis、Stability AI 和 LAION 的研究人员实现并开源的。我们站在 Stable Diffusion 模型应用用户的角度来看，其实没有多么复杂，核心就是根据文本生成图像，其中可以通过一些技巧或者说调整用户参数，来改变文本生成图像的过程，从而达到优化最终生成图像的目的。但是，Stable Diffusion 底层技术的角度看，这个过程非常非常复杂，所以我们这里先给出模型的 Architecture Overview，先从总体上看整个架构或结构是什么样的，然后深入到每一个部分去了解具体的技术细节或原理。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1 模型架构概览&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;从 High-level 的视角，Stable Diffusion 模型都包含哪些主要组件，以及整体的处理流程，我们引用了   &lt;a href="https://jalammar.github.io/illustrated-stable-diffusion/" target="_blank"&gt;The Illustrated Stable Diffusion&lt;/a&gt; 一文中的一个图，并在原图上做了微小改动（为了方便理解添加了表示三个核心步骤的数字序号），来表示 Stable Diffusion 模型的处理机制，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="stable-diffusion-architecture" height="428" src="http://shiyanjun.cn/wp-content/uploads/2023/07/stable-diffusion-architecture.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;基于上图，我们分步骤描述一下 txt2image 处理的整个过程：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;首先&lt;/strong&gt;，输入 Prompt 提示词 “paradise, cosmic, beach”，经过 Text Encoder 组件的处理，将输入的 Prompt 提示词转换成 77×768 的 Token Embeddings，该 Embeddings 输入到 Image Information Creator 组件；  &lt;br /&gt;
  &lt;strong&gt;然后&lt;/strong&gt;，Random image information tensor 是由一个 Latent Seed（Gaussian noise ~ N(0, 1)） 随机生成的 64×64 大小的图片表示，它表示一个完全的噪声图片，作为 Image Information Creator 组件的另一个初始输入；  &lt;br /&gt;
  &lt;strong&gt;接着&lt;/strong&gt;，通过 Image Information Creator 组件的处理（该过程称为 Diffusion），生成一个包含图片信息的 64×64 的 Processed image tensor，该输出包含了前面输入 Prompt 提示词所具有的语义信息的图片的信息；  &lt;br /&gt;
  &lt;strong&gt;最后&lt;/strong&gt;，上一步生成的 Processed image tensor 信息经过 Image Decoder 组件处理后生成最终的和输入 Prompt 提示词相关的 512×512 大小的图片输出。&lt;/p&gt;
 &lt;p&gt;最终使用 Stable Diffusion 模型来进行推理，得到我们需要的根据提示词生成的图像（当然 Stable Diffusion 模型不只是能够实现 txt2image，也可以实现其它的推理功能，如 image2image、txt + imange =&amp;gt; image）。我们通过一个详细的流程图，展示整个推理的过程，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="stable-diffusion-detailed-process" height="769" src="http://shiyanjun.cn/wp-content/uploads/2023/07/stable-diffusion-detailed-process.png" width="578"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面，我们从组件的视角来理解，上面提到的其中三个核心组件，深入到这些组件的内部来了解具体都做了什么处理工作。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2 Text Encoder&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Text Encoder 是用来处理输入的 Prompt 提示词的，将其转换成对应的 Token Embeddings，它使用了由 OpenAI 开发实现的 ClipText 模型，该模型是基于 BERT 实现的预训练语言模型（Language Model），模型包含 63M 参数。如果对语言模型有所了解，从模型中提取 Token Embedding 是最基础的功能，Stable Diffusion 模型使用 ClipText 将输入 Prompt 提示词转换成对应的 Token Embeddings。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3 Image Information Creator&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Image Information Creator 是最核心也最复杂的组件，它是在原始的 image2image 处理流程中进行改造和优化，使用了输入 Prompt 提示词给定的语义信息，从一个随机的 Random image information tensor 经过迭代生成了最终包含图片所有信息的 Processed image tensor。Image Information Creator 包含一个神经网络 UNet 和调度器 Scheduler，实现的核心功能就是 Diffusion，它是在 Latent Space 中实现的，其中包含一个 Forward Diffusion 过程和一个 Reverse Diffusion 过程。  &lt;br /&gt;
为了直观理解，我们将看一下在 UNet 网络训练过程中，初始输入的 Random image information tensor 是如何逐步加入 Prompt 提示词语义信息并影响最终生成的图片质量，假设 UNet 网络去噪（Denoising）迭代 50 次，直接把每次的迭代结果通过 Image Decoder 解码成图像，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="stable-diffusion-denoising" height="418" src="http://shiyanjun.cn/wp-content/uploads/2023/07/stable-diffusion-denoising.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可见，最初的 Random image information tensor 完全是噪声数据，经过一次一次地迭代去噪，注入提示词语义，图片逐渐具备了我们所期望的内容和质量。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.1 神经网络视角：UNet 网络架构&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;UNet 是一个 image2image 的神经网络，更具体一点应该是一个 CNN + inverted CNN 网络，主要目标就是去噪（Denoising），从而生成对应的图像。我们从神经网络的视角，看下 UNet 网络的架构，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="UNet-Architecture" height="404" src="http://shiyanjun.cn/wp-content/uploads/2023/07/UNet-Architecture.jpg" width="850"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;上图中没有体现对应的输入 Prompt 提示词语义信息。在 Stable Diffusion 模型中，UNet 网络各层的构建过程，如下所示：&lt;/p&gt;
 &lt;pre&gt;
(conv_in): Conv2d(4, 320, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) 
(time_proj): Timesteps()
(time_embedding): TimestepEmbedding
	(linear_1): Linear(in_features=320, out_features=1280, bias=True) 
	(act): SiLU()
	(linear_2): Linear(in_features=1280, out_features=1280, bias=True)
(down_blocks):
	(0): CrossAttnDownBlock2D 
	(1): CrossAttnDownBlock2D 
	(2): CrossAttnDownBlock2D 
	(3): DownBlock2D
(up_blocks):
	(0): UpBlock2D
	(1): CrossAttnUpBlock2D 
	(2): CrossAttnUpBlock2D 
	(3): CrossAttnUpBlock2D
(mid_block): UNetMidBlock2DCrossAttn 
	(attentions):
	(resnets):
(conv_norm_out): GroupNorm(32, 320, eps=1e-05, affine=True) 
(conv_act): SiLU()
(conv_out): Conv2d(320, 4, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
&lt;/pre&gt;
 &lt;p&gt;上面有添加对应的提示词语义信息，比如 CrossAttnDownBlock2D、CrossAttnUpBlock2D。为了更详细、直观地表示 UNet 网络中的 Attention 信息，我们通过下图来看图片的 Image Tensor 和输入 Prompt 的 Embedding（Text Attention）是如何一起在 UNet 网络中整合在一起的：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="unet-with-text" height="360" src="http://shiyanjun.cn/wp-content/uploads/2023/07/unet-with-text.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.2 图像转换视角：原始图像 + 提示词 =&amp;gt; 最终图像&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;从图像转换视角看，我们不能不提到 Pixel Space 和 Latent Space。对于最开始输入的图像，它是在 Pixel Space 中，使用像素表示，这个我们能够比较容易感知和理解。当输入的图像、 Prompt 提示词分别被转换成 Image Embeddings、Token Embeddings 之后，后续的操作就开始进入 Latent Space 中，通过向量来表示和并进行各种处理操作，得到了包含 “原始图像 + 提示词” 信息的图片向量数据信息（Latent Image），最后要把这个生成图片向量数据信息，从 Latent Space 再映射到 Pixel Space，得到我们最终需要生成的视觉图像。这最后一步的映射转换是在 Image Decoder 组件中进行的。&lt;/p&gt;
 &lt;p&gt;为了直观，我们描述这个过程是如何一步一步完成的（引用文章   &lt;a href="https://stable-diffusion-art.com/how-stable-diffusion-work/" target="_blank"&gt;How does Stable Diffusion work?&lt;/a&gt; 中给出的分析），步骤如下所示：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第 1 步：输入图像被 Encode 到 Latent Space&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image2image-step-1" height="307" src="http://shiyanjun.cn/wp-content/uploads/2023/07/image2image-step-1.png" width="469"&gt;&lt;/img&gt;  &lt;br /&gt;
原始图像转换成了 Latent Space 中的向量表示，我们称为 Latent Image。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第 2 步：向图像中添加噪声&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image2image-step-2" height="226" src="http://shiyanjun.cn/wp-content/uploads/2023/07/image2image-step-2.png" width="469"&gt;&lt;/img&gt;  &lt;br /&gt;
通过有控制地向 Latent Image 中添加噪声，0 表示没有添加噪声，数字 N 越大表示加入的噪声越多，最后得到的是一个包含噪声的图像的向量数据表示。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第 3 步：输入 Latent Space 中的带噪声图像和 Prompt 提示词，Noise Predictor 预测其中的噪声信息 Latent Noise&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image2image-step-3" height="232" src="http://shiyanjun.cn/wp-content/uploads/2023/07/image2image-step-3.png" width="469"&gt;&lt;/img&gt;  &lt;br /&gt;
上面输出的的结果是一个 4x64x64 的噪声 Tensor。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第 4 步：从 Latent Image 中减去 Latent Noise，得到 New Latent Image&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image2image-step-4" height="203" src="http://shiyanjun.cn/wp-content/uploads/2023/07/image2image-step-4.png" width="469"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第 5 步：VAE Decoder 将 Latent Image 转换成 Pixel Space 中的图像&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;前面 3~4 步骤会循环执行指定次数，这是一个 UpSampling 的过程，得到最终的 Latent Image，最后在执将 Latent Image 转换成最终的图像，如下图所示：  &lt;br /&gt;
  &lt;img alt="image2image-step-5" height="314" src="http://shiyanjun.cn/wp-content/uploads/2023/07/image2image-step-5.png" width="469"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4 Image Decoder&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;最后 Image Decoder 根据得到的 Latent Image，基于 VAE Decoder 生成最终的图像。  &lt;br /&gt;
关于 VAE（Variational Autoencoder），它是一个神经网络，由 Encoder 和 Decoder 两部分组成，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="VAE" height="219" src="http://shiyanjun.cn/wp-content/uploads/2023/07/VAE.png" width="469"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中，Encoder 能够将一个图像压缩到低维空间表示，在 Stable Diffusion 模型中，将原始输入图像通过 Encode 转换成 Latent Space 中的向量表示 Latent Image；Decoder 能够将一个压缩表示的图像向量数据转换成高维空间表示，在 Stable Diffusion 模型中将 Latent Space 中图像的向量表示 Latent Image 通过 Decode 转换成 Pixel Space 中的视觉图像。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;5 参考资料&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://huggingface.co/blog/stable_diffusion" target="_blank"&gt;https://huggingface.co/blog/stable_diffusion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://stable-diffusion-art.com/how-stable-diffusion-work/" target="_blank"&gt;https://stable-diffusion-art.com/how-stable-diffusion-work/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://jalammar.github.io/illustrated-stable-diffusion/" target="_blank"&gt;https://jalammar.github.io/illustrated-stable-diffusion/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://scholar.harvard.edu/files/binxuw/files/stable_diffusion_a_tutorial.pdf" target="_blank"&gt;https://scholar.harvard.edu/files/binxuw/files/stable_diffusion_a_tutorial.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://zhuanlan.zhihu.com/p/628714183" target="_blank"&gt;https://zhuanlan.zhihu.com/p/628714183&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;High-Resolution Image Synthesis with Latent Diffusion Models&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>人工智能 Stable Diffusion</category>
      <guid isPermaLink="true">https://itindex.net/detail/62827-stable-diffusion-%E6%A8%A1%E5%9E%8B</guid>
      <pubDate>Mon, 31 Jul 2023 20:08:24 CST</pubDate>
    </item>
    <item>
      <title>最新进展：Serverless 冷启动优化技术</title>
      <link>https://itindex.net/detail/62812-serverless-%E5%86%B7%E5%90%AF%E5%8A%A8-%E4%BC%98%E5%8C%96</link>
      <description>作者 | 华为云 Serverless 团队策划 | 褚杏娟问题背景 &lt;p&gt;Serverless 计算也称服务器无感知计算或函数计算，是近年来一种新兴的云计算编程模式。其致力于大幅简化云业务开发流程，使得应用开发者从繁杂的服务器运维工作中解放出来（例如自动伸缩、日志和监控等）。借助 Serverless 计算，开发者仅需上传业务代码并进行简单的资源配置便可实现服务的快速构建部署，云服务商则按照函数服务调用量和实际资源使用收费，从而帮助用户实现业务的快速交付 (fast built &amp;amp; Relia. Deliv.) 和低成本运行。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在当前阶段，业界公认的 Serverless 计算的准确定义应该为“FaaS+BaaS”，即 Function-as-a-Service 同 Backend-as-a-service 的组合。Serverless 计算改变了用户使用云的方式，为了实现细粒度的资源供给和高度弹性的扩缩容能力，Serverless 计算提供了无状态函数的编程抽象，即将用户的应用程序构建为多个无状态的函数集合，并通过中间存储、BaaS 等业务组件交互，继而构建完整的云应用。&lt;/p&gt; &lt;p&gt;然而，Serverless 计算的无状态函数编程在带来高度弹性和灵活性的同时，也导致了不可避免的冷启动问题。由于函数通常在执行完请求后被释放，当请求到达时，如果没有可用实例则需要从零开始启动新的实例处理请求（即冷启动）。当冷启动调用发生时，Serverless 平台需要执行实例调度、镜像分发、实例创建、资源配置、运行环境初始化以及代码加载等一系列操作，这一过程引发的时延通常可达请求实际执行时间的数倍。相对于冷启动调用，热调用（即请求到达时有可用实例）的准备时间可以控制在亚毫秒级。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在特定领域例如 AI 推理场景，冷启动调用导致的高时延问题则更为突出，例如，使用 TensorFlow 框架的启动以及读取和加载模型可能需要消耗数秒或数十秒。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;因此，如何缓解 Serverless 函数的冷启动问题，改善函数性能是当前 Serverless 领域面临的主要挑战之一。&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;降低冷启动发生率：通过函数预热、复用或实例共享等方法提高实例的利用效率，减少冷启动调用的发生。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;业界最新进展 &lt;p&gt;在本篇文章中，我们首先从提高实例启动速度方面介绍下业界的前沿进展。&lt;/p&gt;提高实例启动速度 &lt;p&gt;当冷启动发生时，Serverless 平台内部实例的初始化过程可以划分为准备和加载两个阶段。其中，准备阶段主要包括控制面决策调度 / 镜像获取、Runtime 运行时初始化、应用数据 / 代码传输几个部分。而加载阶段位于实例内部，包括用户应用框架和代码的初始化过程。在工业界和学术界公开的研究成果中，针对实例启动过程中的每个阶段都有大量的技术手段和优化方法。如下图所示，经过优化，实例冷启动的准备阶段和加载阶段时间可被极大地缩短。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;下面列举了一些近年来发表在计算机系统领域知名会议的相关工作，主要可以分为五个方面：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;调度优化 / 镜像快速分发 / 本地池化：例如利用文件级镜像缓存复用机制和并行化镜像构建技术，加快容器构建速度 FAST-BUILD [MSST&amp;apos;19]；阿里云基于树结构的跨节点快速镜像分发 FaasNet [ATC&amp;apos;21]；AWS 基于 Block 和分布式多级缓存的镜像分发策略 [Arxiv&amp;apos;23]；Pod 池 + 特化实例跳过镜像传输 [华为云 FunctionGraph]。其中，快速镜像分发依赖于 VM 节点的上 / 下行网络带宽，Pod 池特化技术则是典型的以空间换时间的做法。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;轻量级虚拟化 / 安全容器：例如针对传统容器 Docker 的精简优化工作 SOCK [ATC&amp;apos;21]；更侧重安全性的轻量级虚拟化技术（Kata Containers, gVisor 等)；基于安全容器的进一步的精简优化工作 (Catalyzer [ASPLOS&amp;apos;20], REAP[ASPLOS&amp;apos;21])。通过裁剪优化，安全容器的启动时延最快可以被压缩至亚毫秒级。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;数据共享 / 跨节点传输优化：例如基于 RDMA 共享内存减少跨节点启动过程的数据拷贝 RemoteFork [OSDI&amp;apos;23]；或者利用本地代码缓存跳过代码传输 [华为 FunctionGraph, 字节 ByteFaaS 等]。基于 RDMA 技术的跨节点数据传输时延可降低至微妙级。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;用户代码精简 / 快速加载：例如针对 Java 语言的 JVM（Java Virtual Machine）运行时优化技术 [FunctionGraph]；以及针对 Python 运行时库的裁剪优化工作 FaasLight [arxiv&amp;apos;23]。通过特定的优化，JVM 启动时间可由数秒降低至数十毫秒，而 Python 代码的启动加载时延可降低约 1/3。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;其它非容器运行时技术：例如 WASM（即 WebAssembly）技术以及针对 WASM 的内存隔离方面的优化工作 Faasm [ATC&amp;apos;20]。相比容器化技术，直接以进程和线程方式组织运行函数，可在保证低开销函数运行的同时具备高度灵活性。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;接下来，针对上述 5 个方面的优化工作进行详细介绍。&lt;/p&gt;镜像压缩 / 快速分发 / 本地池化 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;快速请求 / 实例调度。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 Serverless 场景下，由于函数的体积更小、动态性更高，Serverless 平台在负载高峰时也会面临较大压力。因此主流的 Serverless 服务商通常采用“大小流区分的负载均衡 + 本地优先调度”的设计来实现请求的快速分发，降低调度时延。即在流量较小的时候，采用工作负载聚合的方式来分发请求，减少集群资源使用；在流量较大的时候，通过一致性 hash 或轮询等方法将请求快速映射到后端节点，从而降低排队时延。这种“自顶向下”的调度设计简单，易于实现，但是在超大规模的函数并发场景下仍可能面临顶层控制器的性能瓶颈问题，因此，根据实际场景架构适当地探索“自下而上”的调度框架设计将有助于缓解该问题。&lt;/p&gt; &lt;p&gt;此外，也有相关研究人员提出可将 Serverless 集群划分为多个小型的 worker 池，每个 worker 池由一个单独的 semi-global 子调度器管理，降低各个调度器的压力，从而改善调度时延[1] 。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;镜像压缩与快速分发。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 Serverless 平台中，用户的函数镜像通常被存储在 cluster-level 的镜像仓库，节点（通常指虚拟机 Virtual Machine, 简称 VM）创建实例时首先需要将镜像文件通过网络传输到对应的节点（1 个 100MB 的镜像文件从传输到容器创建完成需要耗费约 20 秒 [阿里云，Borg]）。在这个过程中，影响镜像传输时延的因素主要为镜像体积和节点网络带宽。因此，Serverless 平台通常会采用镜像压缩技术缩小镜像体积，例如开源工具 fastfreeze[2] 。除此之外，为了解决节点网络带宽受限导致的拉取镜像时间过长的问题，阿里云的 FaasNet[29] 工作提出了一种基于树状网络的跨节点镜像分发策略，即通过根节点的 VM 将镜像下发给子节点（通常为 2），子节点获取到镜像后以同样方式扩展到更多的节点，从而充分利用节点的上 / 下行带宽实现短时间内快速扩容大量实例的目的。&lt;/p&gt; &lt;p&gt;此外，在节点中设立镜像缓存也是一种有效的方法，可以在一定程度上减少镜像传输操作，例如 AWS Lambda 采用了一种基于 Block 的镜像切分和缓存方法[30]，相比于基于 Layer 的镜像构建方法，基于 Block 的镜像切分粒度更细，可以极大地提高镜像复用度，同时这些镜像 Block 被存储在由本地缓存，分布式缓存和 S3 组成的三级缓存系统中，从而实现快速镜像分发和实例创建速度。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;Pod 池与实例特化。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;类似于本地镜像缓存的概念，在集群中预留包含函数运行时基础镜像的实例（即具备用户代码运行环境的热容器）也是一种常用的“以时间换空间”的做法[3]。这种方法在集群中建立 Pod 池，预先启动一定数量的具有不同资源规格和语言运行时的实例（例如 Java, Nodejs, Python 等）。当冷启动调用发生时，Serverless 平台可以快速从 Pod 池中取出所需要的实例进行特化（挂载代码文件并加载执行），从而跳过镜像获取和运行时环境启动阶段，实现快速创建实例的目的。&lt;/p&gt; &lt;p&gt;生产环境下的数据测算显示，Node.js 实例特化时的启动时间仅需 15ms。然而，在 Pod 池中预留实例会占用大量集群资源，增加云供应商的服务成本。如何权衡用户函数性能和 Pod 池预留成本也是该类方法所面临的一个问题。&lt;/p&gt;轻量级虚拟化 / 安全容器加速 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;传统容器加速。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;相比于传统的 VM 动辄数十秒至数分钟的创建时间，容器的启动速度和运行时开销更小，因此 Serverless 平台通常使用容器技术来隔离用户的函数实例，例如 Docker[4] 。然而在考虑函数的实际运行性能时，Docker 之类的传统容器设计包含了很多不必要的运行开销，因此并不适合直接作为函数沙盒环境。为此，SOCK 工作[5] 基于 Linux container 的设计分析了 Docker 文件系统和网络模块的性能瓶颈，通过裁剪不必要的启动项并对网络模块进行特定优化，大幅降低了 Docker 容器的内核扩展开销（超过 18 倍）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;兼顾安全性的轻量化容器或 microVM。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在非 Serverless 的云场景中，VM 的强安全隔离性使其在云租户的服务部署中扮演着重要角色。在 Serverless 场景中，租户函数间的安全隔离性也同样重要。为了弥补传统容器的安全性不足的问题，研究学者尝试将 VM 的高安全性同容器技术进行融合，例如 Intel 和 HyperHQ 团队主导的 Kata Containers 项目[6] ，Kata Containers 的本质是一个精简后的轻量级虚拟机，在其上面运行着用户的容器进程。为了减少暴露给容器进程的攻击面，Kata Containers 基于传统的虚拟化技术通过硬件虚拟出一个 microVM，而 microVM 里则运行了一个裁剪后的 Linux 内核来实现进程间的强隔离（  &lt;em&gt;注：裁剪即屏蔽掉非必须的系统调用接口&lt;/em&gt;）。AWS Lambda 的 Firecracker[7] 也采用了类似 microVM 的技术路线来实现多租户隔离性和函数性能间的权衡。&lt;/p&gt; &lt;p&gt;除此之外，gVisor[8] 则是谷歌推出的轻量级安全容器项目。gVisor 采用了另一条技术路线，其核心原理是给容器进程配置一个类似“Guest Kernel”的极小的“独立内核”（即守护进程），通过拦截容器进程的系统调用来向宿主机 OS 发起有限的、可控的系统调用，从而提高系统安全性。由于这个独立内核运行在用户态，免去了大量地址空间转换和内核态切换的操作，因此具有较小的性能开销和更好的灵活性，同时又达到了将容器和宿主机 OS（Operation System）隔离的目的。&lt;/p&gt; &lt;p&gt;无论是 Kata Containers 类容器技术，还是 gVisor 安全容器技术，它们实现安全容器的方法在本质上是殊途同归的。这两种技术都采用了“系统分层”的设计思想，即向容器进程分配了一个独立的“操作系统内核”，从而避免了让租户的容器直接共享宿主机的内核。这样，容器进程的攻击面就从整个宿主机内核变成了一个极小的、独立的、以容器为单位的内核，从而有效解决了容器进程发生“逃逸”或者夺取整个宿主机控制权限的问题。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;针对安全容器的进一步加速。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;尽管 gVisor 之类的安全容器极大地弥补了传统容器的安全隔离性不足的问题，然而，安全性提高的同时也带来了额外的性能损失，例如拦截调用会增大启动和运行开销。为此，Catalyzer[9] 基于快照技术和状态重用技术针对 gVisor 安全容器的启动过程进行了进一步优化，大幅缩短了 gVisor 的启动时间。Catalyzer 的设计思想是“避免从零开始启动”，即通过检查点和快照恢复机制跳过启动过程关键路径上的初始化（零初始化），从而实现快速启动的目的。此外，Catalyzer 还提出了一个新的操作系统原语 sfork 技术，通过直接重用正在运行的沙箱实例的状态来进一步减少启动延迟。评估表明，Catalyzer 在最佳情况下可将 gVisor 的启动延迟降低至&amp;lt;1ms。&lt;/p&gt; &lt;p&gt;值得注意的是，尽管 Catalyzer 实现了“用户角度”的快速启动，但是其并不能保证内部所有的系统状态都为最新可用状态，为此，Catalyzer 采用了按需加载机制作为补救措施，即通过在后续运行过程中逐步恢复用户级内存状态和系统状态来最终恢复函数性能。该领域的另一项研究工作 REAP[10] 发现 Catalyzer 的设计机制带来的开销并不是可忽略的，运行过程中频繁的系统缺页在最坏的情况下会导致时延提高接近一倍。为此，REAP 提出了一种页面预加载机制，通过识别函数运行过程中所需的必要工作集并提前载入内存，从而显著改善了 Catalyzer 的缺页开销问题。&lt;/p&gt; &lt;p&gt;  &lt;em&gt;注：该部分涉及的一些术语可以参考   &lt;strong&gt;扩展知识部分&lt;/strong&gt;进行了解。&lt;/em&gt;&lt;/p&gt;数据共享 / 传输优化 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;基于 RDMA 的跨节点分发。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 Serverless 平台内部，借助网络进行跨节点数据、代码或者状态的传输也是一个较为耗时的操作，尤其是在大量实例并发创建的场景中，远程容器初始化的频繁需求进一步加剧了这种现象。为了解决该问题，MITOSIS[11] 工作在支持 RDMA 的操作系统中实现了一个新的操作系统原语，即 remote fork，remote fork 允许不同节点的操作系统之间通过 RDMA 技术进行内存共享映射，从而实现快速远程内存读取和跨节点容器间的数据传输。MITOSIS 可以大幅降低跨节点的实例初始化时间，从单个实例跨节点复制 10,000 个新实例的时间小于 1 秒。然而，MITOSIS 的实现涉及对操作系统内核代码的大量修改，同时还依赖于 RDMA 硬件的支持，这些都使得它在实际部署中会面临着挑战。&lt;/p&gt; &lt;p&gt;除此之外，新型网络设备例如 DPU 硬件等也提供了基于 RDMA 的高速跨节点互联的机制，即通过内存映射和共享来加快网络数据传输速度，这些硬件都可以被探索用于改善 Serverless 平台跨节点数据传输的效率。&lt;/p&gt;代码加载延迟优化 &lt;p&gt;前面章节提到的加速技术都集中在系统层和框架层，在应用层，容器运行时环境的启动和函数代码的加载也在冷启动延迟中占据了相当的比例，尤其是对于机器学习等应用，运行库的加载和框架的启动时间可达数秒[12]。值得注意的是，即便调度时延和容器初始化时间再短，长的用户侧启动时延仍然会使得冷启动问题变得棘手。&lt;/p&gt; &lt;p&gt;为此，Serverless 平台通常会针对函数的运行时进行优化，例如华为的 FunctionGraph 内部针对 JVM 的优化技术可将简单 Java 应用的初始化时间降低至数十毫秒。在学术研究领域，也有针对 Python 函数运行时库的裁剪优化工作例如 FaasLight[13]，FaasLight 的核心思想是“在函数启动过程中尽可能地只加载少量运行必需的代码”。为了实现这一目标，FaaSLight 构建了函数级调用图来识别和筛选与应用程序功能相关的代码，并对非强相关的代码（即可选代码）进行按需加载，避免必要代码识别不准确导致应用失败。实验结果显示 FaaSLight 可最高降低 78% 的 Python 代码加载延迟，平均延迟降低约 28%。&lt;/p&gt;非容器化运行时技术 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;WASM 运行时。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;WSAM（即 WebAssembly）[14]技术也是近年来一种新兴的编程运行时，可以用在云场景中代替部分容器运行时来部署用户函数。WSAM 的运行原理类似于 JVM，可直接运行在宿主机 OS 上，其支持多种语言编码，用户函数代码在运行前需要编译成 WASM 运行时，继而实现跨平台运行。WASM 同样提供了高性能沙盒环境，并具备高灵活性和一定的安全性。&lt;/p&gt; &lt;p&gt;尽管 WASM 有诸多优势，但是也存在着诸如内存隔离性弱这样的问题，因此并不能完全替代现有的容器技术，而是在大多数情况下与 Docker 容器技术互相配合使用。据悉，基于 WASM 的函数计算平台目前在国内著名的云计算厂商内部都有布局，有的平台已经推出了初步的商用版本。&lt;/p&gt; &lt;p&gt;在研究领域，也有针对 WASM 的内存隔离性开展优化的工作，例如 Faasm[15]。Faasm 提出了一个新的隔离抽象“faaslets”，并基于 WebAssembly 的 software-default isolation (SFI) 机制实现了函数间的内存隔离。此外，在 faaslets 的设计中，来自不同租户的多个函数可共享相同的内存地址空间，这样就可以避免函数间通信时大量的数据交换所导致的性能开销。faaslets 还可以与 Linux cgroups 机制配合使用，继而实现例如 CPU, 网络资源等资源的隔离。这些优化措施可以使得 Faasm 获得接近 2 倍的性能加速同时降低内存使用 10 倍以上。&lt;/p&gt; &lt;p&gt;值得注意的是，上述的这些技术手段，无论是容器技术、VM 技术还是 WASM 技术，它们本质上都是以进程方式运行在服务器内部，因此都可以借助快照技术（例如 Linux CRIU[16]）实现更快的系统状态保存和恢复，从而进一步加快初始化速度。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;Unikernal 技术。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;从前面列举的一些技术的特征可以看到，函数运行环境的启动速度和安全性之间总是存在一个折中，即为了改善安全性必须添加一些中间层来增强系统的控制能力，然而中间层的引入势必会降低系统启动速度。那么是否存在能够同时满足快速启动和高安全性的技术方案呢？答案就是 Unikernel[17]。Unikernel 顾名思义，是指定制化的操作系统内核。它的核心思想是将“用户程序和运行必需的库文件统一编译成为一个特殊的、单地址空间的内核镜像”。由于只有一个操作系统进程（这里不再有用户态和内核态的区分），同时编译时去掉了不必要的运行库，所以 Unikernel 运行速度可以非常快。如果将 Unikernel 加载到 VM 运行，也可以实现高安全性和隔离性。&lt;/p&gt; &lt;p&gt;但是 Unikernel 的缺点也十分明显，即灵活性差。由于 Unikernel 是与某种语言紧密相关的，所以一个 Unikernel 只能用一种语言编写，如果用户程序有所变动，则需要重新编译 Unikernel，之后加载并重启 VM。同时，Unikernel 的编码门槛也很高，这与 Serverless 计算的简化编程的设计初衷相悖，也是其难以被广泛应用的原因之一。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;扩展知识介绍&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;传统裸金属、VM 和容器技术的区别&lt;/strong&gt;（在体系结构中所处的层级图、用户态和内核态概念）&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在计算机的体系结构中，各种物理设备例如 CPU 处理器、磁盘、网卡等首先组成计算机的物理硬件，也成为“裸金属服务器”。裸金属服务器的运行需要操作系统的支持，比如我们熟知的 Linux 操作系统。借助操作系统就可以编写运行各种软件例如网页服务器、数据库等来提供外部服务。&lt;/p&gt; &lt;p&gt;在云计算场景中，通过虚拟机共享资源是早期云服务商的主要做法。借助虚拟化技术例如 Microsoft hyper-V、Citrix Xen 等，可以将裸金属服务器虚拟为多个单独的 VM，VM 间共享裸金属服务器（也称宿主机）的物理资源，但是在每个 VM 内对租户来说是独占资源的（例如 CPU，内存和磁盘）。实现这一目的的手段就是借助 Hypervisor（即虚拟机监视器）来管理划分各个 VM 使用的物理资源，实现高安全性和隔离性。Hypervisor 的内部比较复杂，涉及到系统调用、物理地址划分等操作，这里不再展开。&lt;/p&gt; &lt;p&gt;在 Hypervisor 划分出来的每个 VM 内部都可以运行一个独立的操作系统（也称 Guest OS），相比于 VM，宿主机的操作系统则称为 Host OS。Guest OS 对于租户来讲是一个类似本机的操作系统，包含运行程序所需要的各种系统调用库和可执行文件，具体的层级划分如左侧子图所示。&lt;/p&gt; &lt;p&gt;容器则是相比 VM 更为轻量级的虚拟化技术，例如 Linux Container（LXC）和基于 LXC 实现的 Docker 等技术（右侧子图所示）。容器的本质是一个用户态进程，因此它可以直接运行在 Host OS 上，我们称之为 Container Engine。Container Engine 可以创建出多个容器给租户使用，并通过 Linux cgroups 等机制实现 namespace 和租户资源（例如 CPU 和内存）的隔离。不同于 VM 的运作方式，容器内部并没有 Guest OS，而是复用底层的宿主机的操作系统，通过创建文件系统和加载用户程序运行所需要的库来实现运作。因此，相比 VM 来讲，容器的运行方式更加轻便灵活，但是安全性较差（例如某个租户的程序可能导致宿主机 OS 被攻击，继而使得其他租户的程序也无法运行）。为了解决这个问题，早期的云厂商做法通常是将容器部署在 VM 中，从而实现安全性和灵活性的折中取舍。&lt;/p&gt; &lt;p&gt;  &lt;em&gt;注：操作系统通常运行在内核态，权限最高，而租户程序则运行在用户态。用户态程序执行系统调用需要切换至内核态，执行完后再切换回用户态。&lt;/em&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;VM、容器和 Unikernel 对比 （灵活性，启动速度和隔离性）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;前面我们提到，Serverless 平台中函数运行的技术路线有 VM，容器和 Unikernal 几种或是它们的组合。无论哪种方式，基本都在围绕技术方案的灵活性（Flexibility），启动速度（Startup Latency）和隔离性（Isolation Level）进行权衡。&lt;/p&gt; &lt;p&gt;下图总结并列出了这几种虚拟化方案的对比：&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;可以看到，VM 具有高隔离性，灵活性较好，但是启动速度最慢。&lt;/p&gt; &lt;p&gt;而传统容器以用户态进程运行，具有高度灵活性，较快的启动速度，但是隔离性较差。&lt;/p&gt; &lt;p&gt;安全容器是针对传统容器进行了裁剪和进一步控制，具有较高的隔离性，但是启动速度和灵活性有所降低。&lt;/p&gt; &lt;p&gt;WASM 技术类似 JVM 的设计理念，编译完成后即可运行，因此具有更快的启动速度和较高的灵活性，但是隔离性较差。&lt;/p&gt; &lt;p&gt;Unikernel 采用了定制化 OS 和单地址空间，因此可以兼顾快的启动速度和高隔离性，但是灵活性很差。&lt;/p&gt; &lt;p&gt;不难发现，这三个要素的关系非常类似于分布式系统的 CAP 原理，即一致性 (Consistency)，可用性 (Availability) 和分区容忍性 (Partitiontolerance) 在分布式系统中最多只能同时实现两点，不可能三者兼顾。&lt;/p&gt; &lt;p&gt;下表展示了各虚拟化技术的启动延迟等指标的具体对比：&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;注：表格数据引用出处 The serverless computing survey[31]。&lt;/p&gt;小结 &lt;p&gt;在实际场景中，不同的函数计算厂商往往有自己的虚拟化技术方案，往往根据自家产品的业务线，技术积累或者客户群体需求进行综合考虑。在出于商业意图或者个人需要搭建函数计算平台进行研究时，可以根据安全性需求制定自己的技术路线。例如个人或者高校团体研究可以归类为非安全性场景，基于目前的&amp;quot;VM+Kuberneters (k8s)+Docker+runtime 优化&amp;quot;技术栈就可以实现一个可用版本，其中 VM 可以提供资源强隔离性，基于多个 VM 节点组建 k8s 集群，函数计算框架例如 OpenWhisk 或 OpenFaaS 可以依托 k8s 进行部署。尽管同一个 VM 内的多租户共享会导致面临一定的安全隐患，但对于实验环境来说是足够的。&lt;/p&gt; &lt;p&gt;在注重租户间隔离性和强调安全的场景下，AWS Lambda 采用的“裸金属 +Hypervisor+microVM+ 容器”技术路线通过 microVM 实现强隔离性，并且 VM 内一般只允许部署同一个租户的函数来提高安全性。而谷歌采用的“裸金属 +Hypervisor+VM+gVisor”则是另一种技术方案，Hypervisor 和 VM 负责提供硬件虚拟化及资源强隔离性，安全性则由 gVisor 提供。其中，Firecracker 和 gVisor 均已开源。&lt;/p&gt; &lt;p&gt;讨论：VM 在每种技术方案都属于必需品的地位，但是这样势必会导致高开销的存在。那么能不能采用“裸金属 + 轻量级 Hypervisor+Host OS+ 安全容器”或者直接“裸金属 +Host OS+ 安全容器”的技术方案减少中间层的存在呢？其实这种方式并非不可以，比如剑桥大学就提出了 CHERI[18] 指令集扩展从而实现在硬件层面进行内存隔离，通过卸载掉虚拟化软件的部分职责，使得 Hypervisor“变薄”。尽管这项工作只基于模拟器进行了实现，但是也验证了未来更轻量化的 Serverless 计算底座的可行性。&lt;/p&gt;降低冷启动发生率 &lt;p&gt;在文章的上半部分，我们主要介绍了如何加速函数实例初始化过程的一些技术方案，包括通过优化调度和镜像分发策略，采用更轻量级的虚拟化技术，或者借助 RDMA 硬件改善跨节点数据传输等方法，尽管这些方法已经可以将实例运行时环境的初始化的时间压缩至数十毫秒甚至是数毫秒，然而用户侧的延迟却仍然存在，例如程序状态的恢复，变量或者配置文件的重新初始化，相关库和框架的启动。具体来讲，在机器学习应用中，TensorFlow 框架的启动过程往往需要花费数秒，即使实例运行时环境的启动时间再短，应用整体的冷启动时延对用户而言依然是无法接受的（注：通常大于 200ms 的时延可被用户察觉）。&lt;/p&gt; &lt;p&gt;在这种情况下，可以从另一个角度入手解决冷启动问题，即降低冷启动调用的发生率。例如，通过缓存完整的函数实例，请求到达时可以快速恢复并处理请求，从而实现近乎零的初始化时延（例如 Docker unpause 操作时延小于 0.5ms）。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;降低冷启动发生率的相关研究可以分为如下几个方面：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;实例保活 / 实例预留：例如基于 Time-to-Live 的 keepalive 保活机制 [AWS Lambda, OpenWhisk]；或者通过并发配置接口预留一定数量的实例 [AWS Labmda 等]；这些方法原理简单，易于实现，但是在面对负载变化时缓存效率较低。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;基于负载特征学习的动态缓存：例如基于请求到达间隔预测的动态缓存方案 Serverless in the Wild [ASPLOS&amp;apos;20]；学习长短期负载变化特征的动态缓存方案 INFless [ASPLOS&amp;apos;22]；基于优先级的可替换缓存策略 FaasCache [ATC&amp;apos;21]；面向异构服务器集群的低成本缓存方案 IceBreaker [ASPLOS&amp;apos;22]。这些动态缓存方案根据负载特征学习决定实例缓存数量或时长，从而在降低冷启动调用率的同时改善缓存资源消耗。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;优化请求分发提高命中率：例如兼顾节点负载和本地化执行的请求调度算法 CH-RLU [HPDC&amp;apos;22]。通过权衡节点负载压力和缓存实例的命中率来对请求的分发规则进行优化设计，避免节点负载过高导致性能下降，同时兼顾冷启动率。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;改善并发 / 实例共享或复用：例如允许同一函数工作流的多个函数共享 Sandbox 环境 SAND [ATC&amp;apos;18]；使用进程或线程编排多个函数到单个实例中运行 Faastlane [ATC&amp;apos;21]；提高实例并发处理能力减少实例创建 Fifer [Middle&amp;apos;20]; 允许租户复用其它函数的空闲实例减少冷启动时间 Pagurus [ATC&amp;apos;22]。这些实例共享或者复用技术可以同缓存方案结合使用，降低冷启动带来的性能影响。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;接下来，针对上述 4 个方面的研究工作进行详细介绍。&lt;/p&gt;实例保活 / 实例预留 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;实例并发设置和预留。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;商用的 Serverless 平台例如 AWS Lambda[19]、华为云 FunctionGraph[20]都内置了 Time-to-live 实例保活机制即 keepalive 方法。其原理是将执行完请求的实例内存中保活一段时间再释放。如果保活期间有新的请求到达，则可以立即执行并重置剩余的保活时间。除此之外，云服务器通常会向用户提供实例并发设置的接口，用户在部署函数后可以设置预留一定数量的实例（需额外支付费用），从而降低由于突发负载导致的冷启动调用率。然而，这类静态的缓存方法易于实现，但是实例预留数量或保活时长难以根据负载变化进行实时调整，从而导致性能下降或者资源超分浪费。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&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;微软的研究团队通过分析 Azure 的生产环境 trace，发现请求的到达间隔普遍分布在秒级、数分钟乃至数小时，基于 Time-to-live 的静态保活策略会导致大量的资源浪费。因此，作者提出了基于混合直方图的策略来统计函数请求的调用间隔，并根据这些信息动态的释放或提前拉取函数实例，从而在减少冷启动调用率的同时减少 Serverless 平台内部的缓存资源浪费[21] 。然而，这套设计方案只针对 0-1 的函数扩缩场景进行了评估，因为它只控制实例的缓存时长，而无法感知实例数量的变化，如何将这套机制运用在 1- 多的实例扩缩场景中还面临着一些挑战。&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&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;直方图策略通过生成函数请求到达间隔的分布来决定缓存实例的何时创建和缓存时长，然而，在初期历史统计数据不足或者负载发生短期突变的场景下，得到的直方图分布往往会不具有代表性，从而导致缓存策略的实际效果下降。为了解决该问题，INFless[22] 提出了一个长短期结合的混合直方图策略来改善预测精度，通过分别统计长期的直方图分布特征和短期的直方图分布特征，来共同决定缓存实例的创建时间点和保活时长，从而改善缓存效率（冷启动率 / 缓存资源成本）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;基于优先级的可替换缓存策略。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;无论是 Time-to-live 的 keepalive 还是基于直方图的动态缓存方案，都是倾向于减少缓存资源的消耗，从而降低函数服务提供商的运营成本。然而，FaasCache  &lt;sup&gt;[23] &lt;/sup&gt;的作者认为函数计算集群的内存资源与其与闲置，不如用来缓存更多的函数实例，从而最大程度地降低冷启动调用发生率。为此，FaasCache 借鉴了 CPU 缓存的替换算法，通过函数的最近访问时间，函数内存消耗以及冷启动时长为每个实例定义优先级。请求执行结束后的函数实例都会被长期缓存在节点中，仅当节点无可用资源时才驱逐较低优先级的实例来缓存新的函数。此外，FaasCache 还设计了一个反馈控制机制来调整缓存容量的大小，通过权衡冷启动率和缓存资源成本来实现较好的缓存效率。&lt;/p&gt; &lt;p&gt;面向异构集群的低成本缓存策略。&lt;/p&gt; &lt;p&gt;云服务供应商通常会提供多种具有不同 CPU 型号的服务器给租户使用，这些 VM 的处理能力和价格有所差异。IceBreaker[24] 针对异构函数计算集群设计了一个成本优先的缓存策略，其核心思想是“可以使用价格更为便宜的低端服务器缓存冷启动开销较低的函数，从而节省总体成本”。为了实现该目的，IceBreaker 基于傅里叶变化预测函数的负载到达率，并结合实例的使用率和节点加速前亲和性来决定将函数缓存在高端服务器，低端服务器或者不进行缓存，从而以损失部分函数性能的代价降低整体缓存消耗的资源成本。&lt;/p&gt;优化请求分发提高缓存命中率 &lt;p&gt;此外，还有一些工作通过优化或重新设计 Serverless 平台的请求分发策略来改善函数实例的命中率，从而改善缓存效率。在 Serverless 平台内部，前端组件收到请求后，通常以 hash 或负载均衡的策略分发到后端节点进行处理。将同一函数的请求分发到相对固定的节点可以有效地提高节点内实例的使用率（即缓存命中率），这也称为缓存的本地性原理。但是，由于函数负载中通常存在大量的热点函数，当热点函数聚集在后端节点时，则可能导致节点资源使负载压力升高，从而引发性能下降。&lt;/p&gt; &lt;p&gt;因此，CH-RLU[25] 工作提出了一个负载感知的请求调度策略，作者基于一致性 Hash 的分发规则在调度过程中考虑后端节点的压力，通过将高负载的函数分发到不同的节点来避免局部热点， 从而缓解资源竞争导致的函数性能下降问题。可以看到，优化负载分发的研究工作可以同现有的缓存方案结合使用，通过协同设计可以进一步改善 Serverless 平台的缓存效率。&lt;/p&gt;改善并发 / 实例共享或复用 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;多函数共享 Sandbox。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;SAND[26] 工作在函数工作流场景下提出了一种多函数共享实例 Sandbox 的方案，它允许同一个函数工作流内的不同函数共享同样的 Sandbox 环境，从而减少请求执行时创建的函数实例数量，这将有助于减少请求调用的冷启动发生率。然而，这在实际场景中也有很多局限性，例如租户的多个函数可能由不同的语言运行时编写，这些函数往往无法从实例共享中获益。此外，多个函数共享 Sandbox 会增加实例的体积，而工作流中的函数可能被多条路径所调用，在函数扩容时则会导致不必要的资源浪费。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;进 / 线程混合的函数编排。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;传统的函数实例以容器为单位，每个函数运行在一个单独的容器实例内部，容器间的数据传输需要借助网络或第三方存储实现，以 AWS S3 为例，通过 S3 进行函数间数据交换会导致较高时延。为了改善函数实例间的通信效率，Faastlane[27] 工作提出了一个新的函数编排组织方式，即将工作流中的不同函数以进程或线程的形式在容器实例中执行，同时采用 IPC 进程间通信来提高函数交互效率。对于包含敏感数据操作的函数工作流，Faastlane 使用英特尔内存保护密钥 (MPK) 提供轻量级线程级隔离域。在 Apache OpenWhisk 上的评测结果表明，Faastlane 可以将函数工作流的处理时间降低 15 倍，并将函数间的交互延迟减少 99.95%。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;提高实例并发处理能力。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 Serverless 平台中，函数实例通常被配置较少的资源容量，例如内存以 128MB 为单位递增，而 CPU 的配额同内存大小成比例。当请求到达后，平台通常以“一对一”的请求 - 实例映射方式处理请求。低的实例并发处理能力可能导致 Serverless 平台在面临突发负载时创建出大量的实例，不仅降低函数的服务性能，也会增加系统调度压力。为了解决该问题，Fifer [12] 工作提出了一种支持多并发处理的函数实例运行方案，通过为实例分配多个独立的 CPU 处理线程来提高实例的吞吐。同时，通过计算实例的排队延迟，并发处理数和未来请求到达率来提前预置对应数量的实例，从而减少冷启动调用的产生。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;允许租户间实例抢占 / 复用。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;通常 Serverless 平台中的租户函数运行在单独的实例中，不同函数间的实例有独立的资源配置和运行环境。由于不同租户的函数可能具有相同的运行时语言环境，而租户的函数实例也不一定在同一时间都处于忙碌状态，因此，Pagurus[28] 工作另辟蹊径，设计了一个类似“寄居蟹”的租户间实例复用机制，其核心思想是“当 A 租户函数的冷启动发生时，可以迅速抢占 B 租户的空闲实例的运行时环境，从而减少冷启动的时延”，通过实例抢占和复用，从而避免冷启动发生时从零启动的长时延问题。同时，可以看到该做法同 Pod 池特化技术有异曲同工之妙。&lt;/p&gt;小结 &lt;p&gt;可以看到，改善实例的并发能力或是通过实例共享 / 复用都是为了提高已有实例的利用效率，而缓存的方案则是尽可能利用少的资源来预置更多的实例，从而降低冷启动调用的产生。上述这些方法从不同的角度缓解 Serverless 函数冷启动的问题，可以结合使用来改善缓存效率。&lt;/p&gt; &lt;p&gt;在主动缓存的方案中，由于突发流量或者负载的无规律性，基于负载预测来进行缓存还存在较大难度。在实际场景中，通过主动预测 + 优先级缓存调整来实现缓存方案将更有潜力。受限于各家函数计算平台内部的架构，函数共享实例或者抢占复用的实施成本较高，因此开发人员通常会选择更加易于实现的缓存方案例如预热池来改善服务质量。&lt;/p&gt;总    结 &lt;p&gt;Serverless 的无状态设计赋予了函数计算高度弹性化的扩展能力，然而也带来了难以避免的冷启动问题。消除 Serverless 函数的冷启动开销还是从降低函数冷启动率和加速实例启动过程两个角度综合入手。&lt;/p&gt; &lt;p&gt;对于冷启动开销比较大的函数，在函数计算框架的设计机制中进行优化，尽量避免冷启动发生；当冷启动发生时，采用一系列启动加速技术来缩短整个过程进行补救。在 Serverless 平台的内部，冷启动的管理在实践中可以做进一步精细的划分，例如针对 VIP 大客户、针对有规律的负载，或是针对冷启动开销小的函数，通过分类做定制化、有目的的管理可以进一步改善系统效率。&lt;/p&gt; &lt;p&gt;在未来，基于 WASM+ 进程 fork 的技术可能会有助于彻底消除冷启动问题，但是考虑到实际可用性和安全性等问题，短期内的结果可能也是 WASM 和 Container-based 函数并存的折中方案，互相取长补短，因此在当前阶段研究冷启动问题还是有较大价值的。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;[1] Arjun Singhvi, Kevin Houck, Arjun Balasubramanian, Mohammed Danish Shaikh, Shivaram Venkataraman, Aditya Akella. Archipelago: A Scalable Low-Latency Serverless Platform. arXiv:1911.09849, 2019.  &lt;br /&gt;[2] Fastfreeze. https://github.com/twosigma/fastfreeze. 2023.  &lt;br /&gt;[3] 刘方明, 李林峰, 王磊. 华为 Serverless 核心技术与实践 [M]. 北京: 电子工业出版社, 2021.11.  &lt;br /&gt;[4] Docker. https://www.docker.com/. 2023.  &lt;br /&gt;[5] Edward Oakes, Leon Yang, Dennis Zhou, Kevin Houck, Tyler Harter, Andrea C. Arpaci-Dusseau, Remzi H. Arpaci-Dusseau: SOCK: Rapid Task Provisioning with Serverless-Optimized Containers. USENIX Annual Technical Conference 2018: 57-70.  &lt;br /&gt;[6] Kata Containers. https://katacontainers.io/. 2023.  &lt;br /&gt;[7] Alexandru Agache, Marc Brooker, Alexandra Iordache, Anthony Liguori, Rolf Neugebauer, Phil Piwonka, Diana-Maria Popa: Firecracker: Lightweight Virtualization for Serverless Applications. NSDI 2020: 419-434.  &lt;br /&gt;[8] Open-sourcing gVisor, a sandboxed contaienr runtime. https://github.com/google/gvisor. 2023.  &lt;br /&gt;[9] Dong Du, Tianyi Yu, Yubin Xia, Binyu Zang, Guanglu Yan, Chenggang Qin, Qixuan Wu, Haibo Chen: Catalyzer: Sub-millisecond Startup for Serverless Computing with Initialization-less Booting. ASPLOS 2020: 467-481.  &lt;br /&gt;[10] Dmitrii Ustiugov, Plamen Petrov, Marios Kogias, Edouard Bugnion, Boris Grot: Benchmarking, analysis, and optimization of serverless function snapshots. ASPLOS 2021: 559-572.  &lt;br /&gt;[11] Xingda Wei, Fangming Lu, Tianxia Wang, Jinyu Gu, Yuhan Yang, Rong Chen, Haibo Chen. No Provisioned Concurrency: Fast RDMA-codesigned Remote Fork for Serverless Computing. OSDI 2023.  &lt;br /&gt;[12] Jashwant Raj Gunasekaran, Prashanth Thinakaran, Nachiappan Chidambaram Nachiappan, Mahmut Taylan Kandemir, Chita R. Das. Fifer: Tackling Resource Underutilization in the Serverless Era. Middleware 2020: 280-295.  &lt;br /&gt;[13] Xuanzhe Liu, Jinfeng Wen, Zhenpeng Chen, Ding Li, Junkai Chen, Yi Liu, Haoyu Wang, Xin Jin. FaaSLight: General Application-Level Cold-Start Latency Optimization for Function-as-a-Service in Serverless Computing. arXiv:2207.08175. 2022.  &lt;br /&gt;[14] WebAssembly. https://webassembly.org. 2023.  &lt;br /&gt;[15] Simon Shillaker, Peter R. Pietzuch: Faasm: Lightweight Isolation for Efficient Stateful Serverless Computing. USENIX Annual Technical Conference 2020: 419-433.  &lt;br /&gt;[16] Linux CRIU, stands for checkpoint and resotre in userspace. https://github.com/checkpoint-restore/criu. 2023.  &lt;br /&gt;[17] Anil Madhavapeddy, Richard Mortier, Charalampos Rotsos, David J. Scott, Balraj Singh, Thomas Gazagnaire, Steven Smith, Steven Hand, Jon Crowcroft: Unikernels: library operating systems for the cloud. ASPLOS 2013: 461-472.  &lt;br /&gt;[18] Robert N. M. Watson, Jonathan Woodruff, Peter G. Neumann, Simon W. Moore, Jonathan Anderson, David Chisnall, Nirav H. Dave, Brooks Davis, Khilan Gudka, Ben Laurie, Steven J. Murdoch, Robert M. Norton, Michael Roe, Stacey D. Son, Munraj Vadera: CHERI: A Hybrid Capability-System Architecture for Scalable Software Compartmentalization. IEEE Symposium on Security and Privacy 2015: 20-37.  &lt;br /&gt;[19] AWS Lambda. https://aws.amazon.com/lambda/. 2023.  &lt;br /&gt;[20] 华为云 FunctionGraph. https://www.huaweicloud.com/product/functiongraph.html. 2023.  &lt;br /&gt;[21] Mohammad Shahrad, Rodrigo Fonseca, Iñigo Goiri, Gohar Chaudhry, Paul Batum, Jason Cooke, Eduardo Laureano, Colby Tresness, Mark Russinovich, Ricardo Bianchini: Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider. USENIX Annual Technical Conference 2020: 205-218.  &lt;br /&gt;[22] Yanan Yang, Laiping Zhao, Yiming Li, Huanyu Zhang, Jie Li, Mingyang Zhao, Xingzhen Chen, Keqiu Li: INFless: a native serverless system for low-latency, high-throughput inference. ASPLOS 2022: 768-781.  &lt;br /&gt;[23] Alexander Fuerst, Prateek Sharma: FaasCache: keeping serverless computing alive with greedy-dual caching. ASPLOS 2021: 386-400.  &lt;br /&gt;[24] Rohan Basu Roy, Tirthak Patel, Devesh Tiwari: IceBreaker: warming serverless functions better with heterogeneity. ASPLOS 2022: 753-767.  &lt;br /&gt;[25]Alexander Fuerst, Prateek Sharma: Locality-aware Load-Balancing For Serverless Clusters. HPDC 2022: 227-239.  &lt;br /&gt;[26] Istemi Ekin Akkus, Ruichuan Chen, Ivica Rimac, Manuel Stein, Klaus Satzke, Andre Beck, Paarijaat Aditya, Volker Hilt: SAND: Towards High-Performance Serverless Computing. USENIX Annual Technical Conference 2018: 923-935.  &lt;br /&gt;[27] Swaroop Kotni, Ajay Nayak, Vinod Ganapathy, Arkaprava Basu: Faastlane: Accelerating Function-as-a-Service Workflows. USENIX Annual Technical Conference 2021: 805-820.  &lt;br /&gt;[28] Zijun Li, Quan Chen, Minyi Guo: Pagurus: Eliminating Cold Startup in Serverless Computing with Inter-Action Container Sharing. USENIX Annual Technical Conference 2022.  &lt;br /&gt;[29] Ao Wang, Shuai Chang, Huangshi Tian, Hongqi Wang, Haoran Yang, Huiba Li, Rui Du, Yue Cheng: FaaSNet: Scalable and Fast Provisioning of Custom Serverless Container Runtimes at Alibaba Cloud Function Compute. USENIX Annual Technical Conference 2021: 443-457.  &lt;br /&gt;[30] Marc Brooker, Mike Danilov, Chris Greenwood, Phil Piwonka: On-demand Container Loading in AWS Lambda. CoRR abs/2305.13162 (2023).  &lt;br /&gt;[31] Zijun Li, Linsong Guo, Jiagan Cheng, Quan Chen, Bingsheng He, Minyi Guo: The Serverless Computing Survey: A Technical Primer for Design Architecture. ACM Comput. Surv. 54(10s): 220:1-220:34 (2022).&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;strong&gt;    &lt;strong&gt;     &lt;strong&gt;      &lt;strong&gt;       &lt;strong&gt;        &lt;strong&gt;         &lt;strong&gt;          &lt;strong&gt;           &lt;strong&gt;            &lt;strong&gt;             &lt;strong&gt;              &lt;strong&gt;               &lt;strong&gt;                &lt;strong&gt;                 &lt;strong&gt;                  &lt;strong&gt;                   &lt;strong&gt;                    &lt;strong&gt;                     &lt;strong&gt;点击底部                      &lt;strong&gt;阅读原文&lt;/strong&gt;访问 InfoQ 官网，获取更多精彩内容！&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;今日好文推荐 &lt;p&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&amp;mid=2651175282&amp;idx=1&amp;sn=408fd28a8918b1ca2b2947d3988e1c63&amp;chksm=bdb841218acfc8378ce20f6dafc92397120d9bc423166d5642248b57c1724af3fb2b38be67c3&amp;scene=21#wechat_redirect" target="_blank"&gt;OpenAI 遭遇离职潮：员工对 ChatGPT 进展缓慢失望，痛批 CEO 不务正业&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&amp;mid=2651175273&amp;idx=1&amp;sn=88f6bceb13587812efca90a4a8abc02d&amp;chksm=bdb8413a8acfc82c8dae649dcdd8dfbdd58ea07513eab661fc1394604b2ae531f982c4d17cf9&amp;scene=21#wechat_redirect" target="_blank"&gt;阿里改革，P8 成为历史；GPT-4 模型架构泄露；OpenAI 面临最严调查，Altman 惊慌连发 3 推｜Q 资讯&lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&amp;mid=2651175154&amp;idx=1&amp;sn=c1340f2a375abea1147791b592a963d1&amp;chksm=bdb840a18acfc9b7e65a7bad85cd7068d5b4b0705c892b66c192217c9bbe7a9a8c80cc6d6ac8&amp;scene=21#wechat_redirect" target="_blank"&gt;神器还是垃圾？那些用 AIGC 编程的人，实践得怎么样了&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&amp;mid=2651174818&amp;idx=1&amp;sn=4ce6a97c182527b1ec4fddf4c723290d&amp;chksm=bdb84ff18acfc6e78a72b57421fea9e6f82c25c7d21f9c4e225c26a9c108d1b43f699584fc98&amp;scene=21#wechat_redirect" target="_blank"&gt;&lt;/a&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&amp;mid=2651175003&amp;idx=1&amp;sn=c166987c764421ab81eab0f43c5f6d6f&amp;chksm=bdb840088acfc91e919af6a4770cd056ddd51269f99d6a089747a5f3dd9134807930ac8b681b&amp;scene=21#wechat_redirect" target="_blank"&gt;甲骨文火上浇油、SUSE投入1000万美元，多方“围剿”红帽：“红帽负担不起？那我们来！”&lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&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>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62812-serverless-%E5%86%B7%E5%90%AF%E5%8A%A8-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Sun, 23 Jul 2023 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>亚马逊将使用人工智能技术来总结用户评论</title>
      <link>https://itindex.net/detail/62784-%E4%BA%9A%E9%A9%AC%E9%80%8A-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;【TechWeb】6月13日消息，据外媒报道，亚马逊将使用人工智能（AI）技术来总结用户评论，以快速概述消费者对特定产品的看法。&lt;/p&gt; &lt;img src="http://upload.techweb.com.cn/imgs/2023/0613/1686645409610.jpg"&gt;&lt;/img&gt; &lt;p&gt;外媒称，这种创新方法不仅简化了购买过程，而且使客户对他们正在考虑的产品有了宝贵的了解，最终提高了整体客户体验。&lt;/p&gt; &lt;p&gt;不过，据外媒报道，AI生成的总结是基本的，并没有捕捉到评论中表达的全部意见。AI生成的总结将被标记，这样用户就不会被误导。&lt;/p&gt; &lt;p&gt;至于如何处理虚假评论的泛滥，亚马逊表示，它将使用最新的人工智能技术来打击虚假评论，并识别不真实的评论。&lt;/p&gt; &lt;p&gt;在此之前，亚马逊曾尝试根据产品功能提供星级评分，但这次的新功能是迄今为止最方便用户的。（小狐狸）&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25991;&amp;#31456;" border="0" src="http://s1.techweb.com.cn/static/img/20180614.png"&gt;&lt;/img&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>TechWeb</category>
      <guid isPermaLink="true">https://itindex.net/detail/62784-%E4%BA%9A%E9%A9%AC%E9%80%8A-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Tue, 13 Jun 2023 16:38:33 CST</pubDate>
    </item>
    <item>
      <title>超大规模数据库集群保稳系列之三：美团数据库容灾体系建设实践 - 美团技术团队</title>
      <link>https://itindex.net/detail/62776-%E8%B6%85%E5%A4%A7-%E6%A8%A1%E6%95%B0-%E9%9B%86%E7%BE%A4</link>
      <description>&lt;div&gt;    &lt;h2&gt;1 容灾介绍&lt;/h2&gt;    &lt;p&gt;我们通常会把故障分为三大类，一是主机故障，二是机房故障，三是地域故障。每类故障都有各自的诱发因素，而从主机到机房再到地域，故障发生概率依次越来越小，而故障的影响却越来越大。&lt;/p&gt;    &lt;p&gt;容灾能力的建设目标是非常明确的，就是要能够应对和处理这种机房级和地域级的大规模故障，从而来保障业务的连续性。近几年，业界也发生了多次数据中心级别的故障，对相关公司的业务和品牌产生了非常大的负面影响。当前容灾能力已经成为众多IT企业建设信息化系统的必选项。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/fb4e27cd9aafa3e2c76606ea86cca19a475290.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h2&gt;2 业务容灾架构&lt;/h2&gt;    &lt;h3&gt;2.1 容灾架构演进&lt;/h3&gt;    &lt;p&gt;容灾架构从最早期的单活形态（同城主备）到同城多活形态，再演化到异地多活，根据这个路径可以将容灾分为容灾1.0、容灾2.0、容灾3.0三个阶段。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;容灾1.0&lt;/strong&gt;：容灾体系围绕数据建设，多以主-备的方式部署，但备用机房不承担流量，基本上都是单活结构。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;容灾2.0&lt;/strong&gt;：容灾视角从数据转换为应用系统，业务具有同城双活或同城多活能力，采用同城双活或同城双活加异地冷备（两地三中心）的部署架构，除冷备以外的每个机房都有流量处理能力。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;容灾3.0&lt;/strong&gt;：以业务为中心，多采用单元化架构，容灾基于单元间的两两互备实现，根据单元的部署位置可以实现同城多活和异地多活。采用单元化架构的应用本身具有很好的容灾能力和扩展能力。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/5a6472577564e07f6570c0d521e003df526010.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;由于各公司所处发展阶段不同，采用的方案也会有所区别，美团大部分业务处于2.0阶段（即同城双活或多活架构），但对于大体量、有地域容灾及有地域扩展性要求的业务则处在容灾3.0阶段。下面会介绍一下美团的容灾架构。&lt;/p&gt;    &lt;h3&gt;2.2 美团容灾架构&lt;/h3&gt;    &lt;p&gt;美团的容灾架构主要包括两种，一种是N+1容灾架构，一种是SET化架构。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;N+1架构&lt;/strong&gt;：在业界也称散部或者多AZ部署⽅案，将容量为C的系统部署在N+1个机房，每个机房能提供至少C/N的容量，挂掉任何一个机房时，剩余系统仍能支撑C的容量。该方案的核心是把容灾能力下沉到PaaS组件来完成，在出现机房级或者地域级故障的时候，由各个PaaS组件独立完成容灾切换，实现业务恢复。整体架构如下图所示，业务上表现是多机房、多活形态，数据库采用这种主从架构，单机房处理写流量、多机房的负载均摊读流量。下面要讲“数据库容灾体系建设实践” 就是面向N+1架构的。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/c36bcca2ee86e62009ffd11e1883f0f3190677.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;单元化架构&lt;/strong&gt;：也叫SET化架构，这是一种偏应用层的容灾架构，它将应用，数据，基础组件按照统一的维度切分成多个单元，每个单元处理一部分闭环流量。业务以单元作为部署单位，通过单元互备方式实现同城容灾或者异地容灾。一般金融业务或者超大规模的业务会选择此类架构，它的好处就是流量可以闭环且资源隔离，具有很强的容灾能力和跨域扩展能力，不过SET化架构的落地需要业务系统做大量的改造，运维管理也较为复杂。简化示意图如下：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/73b76a17cbcba274789e571a12834c7a363087.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;美团内部的大部分业务都是N+1架构，外卖和金融等业务采用了单元化架构。总体上美团内部既有同城多活，也有异地多活，两种容灾方案并存。&lt;/p&gt;    &lt;h2&gt;3 数据库容灾建设&lt;/h2&gt;    &lt;h3&gt;3.1 面临的挑战&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;超大规模的集群带来的挑战&lt;/strong&gt;：公司业务高速发展，服务器规模指数级增⻓，数据中心规模越来越大，大机房已有大几千数据库集群，上万个实例。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;性能问题&lt;/strong&gt;：高可用系统的故障并发处理能力出现明显瓶颈。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;容灾失效风险&lt;/strong&gt;：管控链路随集群数量的增加变的越来越复杂，一个环节出问题就会导致整体容灾能力失效。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;故障频发&lt;/strong&gt;：集群数量和规模变大，使原来概率很低的大规模故障变成了稀松平常的故障，其发生的频次和概率越来越高。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;strong&gt;演练成本高、频次低&lt;/strong&gt;：核心能力验证不充分，大规模故障的应对能力处于不可知状态，已知容灾能力“保鲜”困难。拿应对机房级大规模故障的相关能力来讲，很大一部分是处于不可知状态或者仅存在于“纸面”分析中，而对于已验证过的能力随着架构演进迭代，“保鲜”也很困难。&lt;/p&gt;    &lt;p&gt;数据库作为有状态的服务之一，本身建设应对大规模故障能力的难度和挑战都相对更大。&lt;/p&gt;    &lt;h3&gt;3.2 基础高可用&lt;/h3&gt;    &lt;p&gt;数据库架构 在美团主要有两种一种是主从架构，一种是MGR架构。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/2621c1b5737fad2c4a5800606c818e5f719952.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;主从架构&lt;/strong&gt;：应用通过数据库中间件访问数据库，在故障发生时，高可用做故障探测、拓扑调整、配置下发，进而应用恢复。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;MGR架构&lt;/strong&gt;：应用也是通过中间件访问数据库，不过中间件对MGR做了适配，内部叫Zebra for MGR，中间件自动进行拓扑探测感知，一旦MGR发生了切换，新拓扑会被探测到，数据源会进行调整，进而业务恢复。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;美团的高可用架构&lt;/strong&gt;：美团主从集群的高可用是基于Orchestrator二次开发的，本质上是一个中心化的管控架构，如下图所示，有多个高可用分组，每个分组托管一部分数据库集群，分组在北京和上海实现两Region部署，底层核心组件只在北京部署，比如我们的核心CMBD、WorkflowDB等，一旦北上专线出现问题，上海侧的高可用会失效不可用。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/38b23a112b97f2928e68b598f5cb1451451449.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;3.3 容灾建设路径&lt;/h3&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;/ul&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/7c9532564bd5bf7344878341d8f0d841434076.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;3.4 平台能力建设&lt;/h3&gt;    &lt;p&gt;为了建设提升数据库服务的容灾能力，内部成立了容灾管控项目DDTP（Database Disaster Tolerance Platform），专注提升数据库应对大规模故障的能力，核心包括基础容灾管控和故障演练两大能力，分别对应两个平台产品：一是容灾管控平台，一个是数据库演练平台。&lt;/p&gt;    &lt;p&gt;容灾管控平台主要专注于防守，它的核心功能主要包括事前逃生、事中观测以及止损、事后恢复等，数据库演练平台则专注于进攻，支持多种故障类型和多种故障注入方式，具备故障编排，故障复盘等核心能力。这个系列的第二篇      &lt;a href="https://tech.meituan.com/2023/05/26/database-attack-and-defense-practice.html"&gt;《数据库攻防演练建设实践》&lt;/a&gt;就是对演练平台的详细介绍。接下来，我们将重点介绍一下容灾管控平台的主要内容，首先看一下全景图：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/c1a347778b22d1a4dec722011f5c8607299314.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;数据库服务&lt;/strong&gt;：包括MySQL、Blade、MGR等基础数据库服务。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;基础能力层&lt;/strong&gt;：主要是备份恢复、资源管理、弹性伸缩、主从高可用以及指标监控能力，这些能力是稳定性保障的基本部分，但在容灾场景下需要进一步加强，以处理大规模故障场景。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;管控编排层&lt;/strong&gt;：核心是运维编排服务OOS（Operation Orchestration Service），会把基础能力按需编排生成对应的处理流程也叫服务化预案，每个预案对应一个或者多个具体的运维场景。容灾预案也在这个范畴。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;平台服务层&lt;/strong&gt;：是容灾管控平台的能力层，包括：1）        &lt;strong&gt;容灾管控&lt;/strong&gt;，容灾计算评估和隐患治理，还有故障前容灾逃生、故障中的兜底切换，故障摘流等。2）        &lt;strong&gt;容灾观测&lt;/strong&gt;，明确故障范围，支持故障中的容灾决策。3）        &lt;strong&gt;容灾恢复&lt;/strong&gt;，故障后通过实例修复、集群扩容等功能快速恢复集群的容灾能力。4）        &lt;strong&gt;预案服务&lt;/strong&gt;，包含了常见故障应急预案的管理和执行等等。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;3.4.1 容量达标&lt;/h4&gt;    &lt;p&gt;数据库建立了一套N+1容灾计算标准，分为6个等级，如果集群容灾等级≥4级则容灾达标，否则容灾不达标。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/c841b10764c877020a2af7e35eafd759300460.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;从标准可以看出，从等级3开始就是多机房部署了。3级和4、5级的区别是，3级不满足N+1要求，即如果一个机房的节点都出问题，剩余节点无法承担峰值流量。等级4、5都是具备N+1要求的，等级5会满足region间容量对等。除基础标准以外，SET化集群有特殊规则，比如路由策略要闭环、SET集群的绑定机房要统一、互备SET容量要对等、集群内机型要统一等。这些规则都会纳入容灾计算来确定集群的最终容灾等级。&lt;/p&gt;    &lt;p&gt;在基础容灾数据建设中，会把上述规则代码化、计算流程化，通过近实时的方式做基础数据“保鲜”。容灾数据是容灾管控平台上用于逃生切换和事中止损的基础数据，同时还会基于容灾数据建设风险隐患（即容灾不达标隐患），并通过一定的运营治理来消除这种隐患。&lt;/p&gt;    &lt;h4&gt;3.4.2 故障前逃生&lt;/h4&gt;    &lt;p&gt;故障前逃逸能力就是批量主库切换和从库摘流，主要用于在故障前收到预警，提前感知灾难来临，快速将一个机房的所有数据库服务切走或者下线从库流量，以降低真实故障带来的影响。&lt;/p&gt;    &lt;p&gt;我们知道对于主从架构的集群，如果因为断电或者断网发生故障切换，很可能会发生数据丢失。数据一旦丢失，业务需要进行确认并做善后工作，有时候会非常繁琐。如果能够在事前逃走就会把这些风险都规避掉。同时除了主库逃走以外，从库也可以提前把流量“摘掉”，从而做到故障对业务方“无感”。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/ce887fd7cd6a3c1e3465d46ed91236fc530699.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/0340dd86245b70819ad3244fc009b8c7384027.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h4&gt;3.4.3 故障中观测&lt;/h4&gt;    &lt;p&gt;在大规模故障发生的时候，一般会出现告警轰炸，电话咨询轰炸等情况，如果没有全局的故障感知能力，就会使故障处理比较混乱，处理时间比较长，让故障影响放大，所以我们建设了容灾观测大盘，它能够实时、准确、可靠地对故障进行观测，以确保值班同学能够掌握故障的实时情况。&lt;/p&gt;    &lt;p&gt;如下图所示，如果发生了故障，可以快速拿到故障集群或者实例列表，并在对应的页面上发起兜底切换动作，进而实现快速止损。对观测大盘的核心诉求就是要实时、准确、可靠。可以通过减少服务依赖来提升自身的可用性。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/e60dd94c861c909a2128fc8fc88ed100540674.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h4&gt;3.4.4 故障中止损&lt;/h4&gt;    &lt;p&gt;在介绍故障中的止损之前，先了解一下预案服务。预案服务的核心功能就是管理常见故障以及对应的各种处理预案，并提供执行控制能力，让预案可以方便、可控地运行。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/561a918615073403c930c95a9312c5ac265039.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;故障止损&lt;/strong&gt;：在有了预案以后，我们就可以进行兜底止损。如下图所示，当大规模故障发生的时候，HA会自动进行故障处理。如果集群切换失败或者漏切，那么它就会进入兜底阶段。首先从DDTP平台化兜底，如果平台受故障影响不可用，可以在运维编排层进行兜底。如果运维编排服务也失效，则需要人工通过CLI工具进行兜底。CLI是DBA最底层的工具，它和高可用是两个独立的链路。CLI会进行集群拓扑探测、选主选举、拓扑调整、配置修改、配置下发等逻辑，等同于手工集群切换步骤。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/805f625d5d04bbfe8592c5665e32d85f164310.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/996aa357410aceef14126b7790311ac5502146.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;总体原则优先提升高可用自动切换的成功率，减少透传到兜底阶段的集群数量。其次提升预案可靠性，优先选择白屏，逐级下沉，易用性下降，可靠性提升。&lt;/p&gt;    &lt;h4&gt;3.4.5 故障后恢复&lt;/h4&gt;    &lt;p&gt;虽然集群具备N+1能力，一个机房故障的时候，集群剩余节点是能够支撑峰值流量，但它不具备再一次AZ故障的容灾能力，所以在故障后会根据各机房的资源情况，通过容灾决策中心快速进行集群扩容来补齐核心集群的容灾容量，使其重新具备AZ容灾能力。&lt;/p&gt;    &lt;p&gt;上述方案有一个比较大的弊端就是需要有足够的资源来进行扩容，这是非常困难的，目前我们在建设更快速的恢复能力，如实例原地修复，集群原地扩容等，在AZ恢复之后，可以快速复用发生故障的机房内的机器资源，实现容灾快速恢复。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/95a9626104207eb46337ec93e1ac27cb625774.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;3.5 演练体系建设&lt;/h3&gt;    &lt;p&gt;各项基础容灾能力不能只存在于架构设计、理论评估层面，必须实打实的可用，这就要需要通过演练进行验证。容灾管控项目之初，就制定了以演练为抓手的策略，验证并驱动各项基础能力的提升。 截止目前，已经初步建成了多环境、高频次、大规模、长链路的演练体系。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p0.meituan.net/travelcube/e382dc79f8271564c54a723f40c3e9ec230951.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;多环境&lt;/strong&gt;：我们建设了多种演练环境，满足各个PaaS组件的各类容灾演练需求。一是容灾管控平台的⻓稳环境，二是线下专用于演练的隔离环境，三是生产环境，有演练专区以及正常生产环境。&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;：整个容灾链路涉及到很多组件，包括CMDB数据库、流程数据库，高可用组件，配置中心、预案服务等，我们会逐步把这些组件都纳入演练，可以让一个或者多个组件服务同时故障，发现潜在问题，验证多服务的多节点同时故障对于整个故障处理能力的影响。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;3.5.1 隔离环境演练&lt;/h4&gt;    &lt;p&gt;隔离环境演练顾名思义，它是一套和生产机房完全隔离的演练环境，有自己独立的TOR、机柜，风险能做到完全隔离，可以开展独立断网或断电操作。参与演练的PaaS组件和业务服务要在该环境独立部署。在隔离环境除了会定期开展各种容灾演练发现容灾问题外，还可以验证各PaaS的独立部署能力，为国际化业务支撑提供基础。&lt;/p&gt;    &lt;h4&gt;3.5.2 生产环境演练&lt;/h4&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;常态化、大规模故障演练&lt;/strong&gt;：此类演练是日常持续开展的，通过演练平台对数据库集群注入故障，高可用进行故障切换。通过不同的演练规模来验证高可用的并发切换能力。此外，在容灾管控平台上，可以验证逃生能力、止损预案、及大规模故障的观测等。总而言之，它是利用“攻”和“防”相结合的形式，实现能力的验证验收和优化提升。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;这类演练主要特点：一是参演集群都是由生产环境的高可用分组进行托管，就是说演练验证的都是生产环境的高可用的能力；二是参演的大规模集群是非业务集群，是每次演练前新创建的专门用于演练的集群，规模可以做到很大，目前可以常态化的进行1500+集群同时进行演练；三是有一定的仿真效果，为使演练更为真实并对RTO做精准评估，对演练集群增加了带载能力。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/143e9a2ea237e36c557ba9cbc4aee086797626.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;真实专区演练&lt;/strong&gt;：上文介绍的隔离环境演练、大规模演练都是偏模拟性质的，和真实的故障场景有比较大的区别。为了弥补和真实故障主键的GAP，我们基于公有云构建了一个专用演练AZ，可以理解为就是一个独立的机房。参演业务和组PaaS件将部分承载业务流量的服务节点部署到演练AZ中，实际演练的时候会进行真实的断网，业务和组件可以在断网的时候观测和评估自己的容灾情况。这种通过真实机房、真实组件集群、真实的业务流量来验证组件和业务的实际容灾情况，会更加真实。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;img alt="" src="https://p1.meituan.net/travelcube/3e34908029a5e3a999c8574baaa9e563145798.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;Game Day&lt;/strong&gt;：此外我们还在评估论证在真实机房开展演练的可行性，随着隔离环境演练、专区演练的常态化开展，各个组件的基础容灾能力会越来越强，在真实机房进行常态化机房演练的终极目标也会随之达成。&lt;/li&gt;&lt;/ul&gt;    &lt;h2&gt;4 未来思考&lt;/h2&gt;    &lt;p&gt;经过两年多的建设，虽然在高可用自动切换、容灾能力运营治理、大规模故障观测、故障止损预案、容灾恢复等方面取得了一定的成果。但是还有很多能力短板需要建设补齐，业务新的发展也带来了新的需求和挑战。未来我们会补齐短板、迭代技术架构两个方向上进行持续的提升。&lt;/p&gt;    &lt;h3&gt;4.1 补齐短板&lt;/h3&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;strong&gt;超大规模逃生能力、止损能力不足&lt;/strong&gt;：随着我们自建数据中心的落地，我们自建的AZ规模会更大，这对能力的要求会更高，我们主要通过平台迭代和演练验证逐步提升能力。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;跨域专线故障导致Region级高可用失效&lt;/strong&gt;：接下来我们会探索单元化方案或者独立部署方案，实现Region级或者更细粒度的闭环管理。&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;h3&gt;4.2 迭代架构&lt;/h3&gt;    &lt;p&gt;数据库相关技术发展很快，比如Database Mesh、Serverless等新技术形态会逐步落地，届时中间件、高可用、内核等会有比较大的变化，新型客户端HA方案的建设成熟及新Proxy架构，存计分离产品的引入都会使容灾的能力发生比较大的变化。容灾能力建设会随着这些确定的产品演进进行迭代。&lt;/p&gt;    &lt;p&gt;容灾建设是一件非常有挑战的事，也是所有公司业务发展壮大后必须面对的一件事。欢迎大家在文末留言，跟我们一起交流。&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62776-%E8%B6%85%E5%A4%A7-%E6%A8%A1%E6%95%B0-%E9%9B%86%E7%BE%A4</guid>
      <pubDate>Fri, 09 Jun 2023 23:59:45 CST</pubDate>
    </item>
    <item>
      <title>吴军：ChatGPT不算新技术革命，带不来什么新机会</title>
      <link>https://itindex.net/detail/62730-%E5%90%B4%E5%86%9B-chatgpt-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;div&gt;    &lt;img&gt;&lt;/img&gt;作者：吴军，原腾讯副总裁、硅谷风险投资人。（来源： 學人Scholar）    &lt;br /&gt;    &lt;br /&gt;    &lt;p&gt;4月3日晚上，得到直播间邀请到了计算机科学家、自然语言模型专家吴军，就人工智能和ChatGPT等当下热议的话题作了一次直播分享。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q1：ChatGPT的出现，为什么会引起恐慌？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;我知道，最近ChatGPT这事儿在中国很火，很多人在讨论，但很有意思的是，其实这件事在美国，已经没有太多人去谈论这个话题了。其实不光是ChatGPT，往前看十年，当时很多新技术出现的时候，我就发现在中国媒体上讨论的热度要远远高于美国。虽然那个技术其实主要出现在美国，但是中国人老百姓对此更关心。我认为这是一件好事，但也是一件坏事。&lt;/p&gt;    &lt;p&gt;这个“坏”在于，这些技术实际上是被过度的炒作了，在这个过程中，有很多浑水摸鱼的人从中赚钱。就比如说区块链，当时炒得那么热，但如今这个事已经很少有人讨论了，对吧？这是第一个。第二个就是元宇宙，目前美国只有 Facebook一家还在坚持做。那到了中国，很多人就在讨论说，我们是不是将来会生活在一个完全虚拟的世界。最后，大概去年底到今年初，Facebook在这个领域几百亿美元投下去，一个响也没听着，最后开始了大规模的裁员。到了现在，被热炒的一个话题就是ChatGPT，有的人兴奋，有的人恐惧，还有我现在也看到在中国还有很多人在浑水摸鱼，试图再割大家一次韭菜。&lt;/p&gt;    &lt;p&gt;在讲ChatGPT是什么以前，我先给大家讲一个历史故事，这历史故事你听起来你就会发笑，但是你回头看，今天很多人的表现也是如此。&lt;/p&gt;    &lt;p&gt;1503年，哥伦布的儿子记下来的这么一件事儿，哥伦布往西航行，去往新大陆，结果航行到中途，到了牙买加这个地方，船上就没吃的了。于是，哥伦布和船员只能寄希望于当地人来提供饮食。但是，提供了几天以后，船员就跟当地人发生了矛盾——有些船员偷了当地人的东西，所以当地人就断了饮食的供应。&lt;/p&gt;    &lt;p&gt;为了摆脱这个困境，哥伦布想到了一个妙招。哥伦布当时随身带着一本万年历，在日历上标着说某年月日会出现日食、月食等等所有这些信息。哥伦布当时就把当地的部落首领找来，说你们不给我提供食物，已经得罪了上帝，上帝会发怒，月亮就会变红，然后上帝就会把月亮收走。&lt;/p&gt;    &lt;p&gt;当然，我们现在基本上都知道，在月全食发生的时候，也就是地球还没有完全挡住月亮的时候，月亮确实是红的，就是我们所谓的“血月”。但是，当时的牙买加人并不知道。结果，到了晚上，牙买加人就发现，月亮果然变红了，然后慢慢地就一点点消失了。当地人就陷入了恐慌，大家纷纷说，上帝要惩罚他们了。&lt;/p&gt;    &lt;p&gt;这个部落首领慌忙去求哥伦布，承诺答应哥伦布的所有条件。哥伦布就说，好，我去帐篷里向上帝祷告，让他不惩罚你们，但是我需要一点时间，然后哥伦布就走进了帐篷。其实，进了帐篷之后，哥伦布就是拿着一个沙漏，在看那个计时。&lt;/p&gt;    &lt;p&gt;今天咱们有天文学知识，肯定知道月全食的时间，也就会维持大概48分钟，到时候月亮就会重新出现。但是，这些牙买加人不知道。他们看到的就是，哥伦布从帐篷里出来，月亮也就出来了。然后哥伦布说，这是上帝已经听了我的劝解，答应宽恕你们，但是你们必须要给我们好好地提供食物。所以，当地人千恩万谢，给他们不断提供食物。&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;Q2：ChatGPT的技术基础是什么？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;从历史回到现在，其实ChatGPT的情况也差不多，背后是一个叫做语言模型的一个数学模型在发挥作用。换句话说，ChatGPT的背后是一个数学模型。在今天，这项技术显得很强大的原因主要是三个：&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;它是1972 年，由我的导师贾里尼克(Fred Jelinek)带领团队研发的一项技术。具体地讲，是他当时在IBM带着人来完成的一项技术，是用来衡量一句话或者一个语言现象有多么的可能产生。那它有什么用？它最初的用处是做的语音识别，后来是做机器翻译，再后来是做计算机问答，也就是我们今天熟悉的回答问题。&lt;/p&gt;    &lt;p&gt;当时它就可以做摘要，比如举一个例子，有一篇一万字的文章，那么你怎么摘要出十句话能概括这一篇文章的内容，这对于做这个自然语言处理的人来讲，就是一个数学问题。也就是说，你的条件是什么？条件是这一万个字，然后你想得到的结果是什么？结果可能就是十句话，一百个字。然后这里头有很多种组合，你可以随便挑几个句子，也可以把有的句子拆成两段，把后面那些不太重要的修饰或者形容的部分去掉。然后，你也可以把两个句子合成一个句子，那么你在合成一段文本的时候，这个计算机就会计算一个概率，哪些句子合成在一起的概率比较大，它会按照概率帮你合成。&lt;/p&gt;    &lt;p&gt;而我们今天看到的ChatGPT，就是这个大的语言模型，它就是会挑一个概率最大的、最有可能发生的这样一个文本来给你看。所以总体来讲，ChatGPT生成结果的过程，是一个用大量的计算资源来计算的过程。它需要非常庞大的数据量来支撑，有很多很多的GPU（电脑处理器）。没有这些东西的话，ChatGPT是做不起来的。&lt;/p&gt;    &lt;p&gt;而且今天这个ChatGPT，其实不光是技术，还有很多人工在背后。他们还雇了一家公司，专门负责审核ChatGPT产生的结果。比如说，ChatGPT产生了一百篇摘要，都挺好，我已经分辨不出来了，那么这些人就负责帮我分辨一下，到底哪一篇更像是准确的摘要。&lt;/p&gt;    &lt;p&gt;那实际上，你可以看到，ChatGPT背后就是一个语言模型，而这一语言模型的技术是1972年就已经有了的。到现在，经过了五十年，现在行业内其实大家并不觉得它是一个什么了不得的东西。在此以前，这个语言模型其实已经做了很多的事情。&lt;/p&gt;    &lt;p&gt;提到“语言模型”（language model）这个词，最初是由我的导师贾里尼克提出来的。他大概在1993年的时候到了约翰霍普金斯大学，我是1996年到这个大学，然后成为他学生。那么这个词的中文，也就是你看到的“语言模型”这四个字，则是我在20世纪90年代的时候发表论文时候创造出来的。那时，只有我们这些圈内的人知道它能做很多事，但是你不会想到说，哎，这个事后来会被热炒。&lt;/p&gt;    &lt;p&gt;你可以这样理解，“语言模型”之于ChatGPT，就相当于开普勒的这个行星三定律之于月食。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q3：“语言模型”诞生之初是什么情况？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;那么在发明的当时，语言模型是一个什么情况？&lt;/p&gt;    &lt;p&gt;其实，在20世纪90年代的时候，用简单统计方法得到的模型很不准确。这就相当于，我打个比方，你观察行星，但用的是托勒密的地心说来预测，是很不准确的。所以，那时候我们开始引入了语法、主题、语义的很多信息。然后，这个语言模型就变得很复杂了。复杂之后就又带来了一个很大的问题。&lt;/p&gt;    &lt;p&gt;什么问题？&lt;/p&gt;    &lt;p&gt;比如，我当时做过一个很复杂的语言模型，这个语言模型当时有多少参数？600万个参数，就是说，这个语言模型大小基本上按这个参数来定。我那时候做的已经是那个时代能做的最大、最复杂的语言模型了。我当时用的还不是PC机，而是20台超级服务器，然后大概算了三个月才训练出这样一个语言模型。所以你看，它的计算量是非常大的。那么，第一版ChatGPT，它用的语言模型参数是多少呢？大概是 2000 亿个参数，大家可以看到这些年的变化。&lt;/p&gt;    &lt;p&gt;所以，今天很多人问，ChatGPT在美国出现了，中国研究机构什么时候能做ChatGPT？其实，中国的大部分研究机构是做不了的，不是说研究水平的问题，而是因为ChatGPT太耗资源。今天的ChatGPT，可能光硬件的成本就要差不多10亿美元，这还没算电钱，所以成本和耗资是非常巨大的。所以，如果开完玩笑，问ChatGPT的最大贡献是什么，我倒觉得它对全球变暖是有很大贡献的。&lt;/p&gt;    &lt;p&gt;所以，我想说的是，ChatGPT这件事，它的原理很简单，但是在工程上要想做到，其实是蛮困难的一件事。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q4:  计算机擅长回答什么问题？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;到了大概2010年前后，也就是13年前，语言模型能做到什么程度？我给大家看两个例子。这两个例子都是我在2014年离开Google以前做的。当时我负责的是Google的自动问答系统，就是让计算机回答问题。不过因为这个产品是英文的，所以在中文世界基本上没有太露脸。&lt;/p&gt;    &lt;p&gt;我给你看一下谷歌回答的一个问题——为什么天是蓝色的，why is the sky blue？&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;它的回答是这样的：太阳光透过大气层到达地球时会发生折射，空气中的气体会让不同颜色的光散射到各个地方，蓝光波长短，比其他颜色折射率高，所以看上去天是蓝色的。&lt;/p&gt;    &lt;p&gt;这是当时计算机产生的一个答案。公平地讲，这个答案比我自己写一段答案写得要更好，因为要解释这现象，你要知道不少物理学知识，而且这个句子看上去也挺合情合理的。而今天人们使用ChatGPT的一个目的，就是让他回答问题。&lt;/p&gt;    &lt;p&gt;这里面，我给大家做个拆分。&lt;/p&gt;    &lt;p&gt;其实，我们问计算机的问题可以分为两类，第一类叫做简单问题，第二类叫做复杂问题。简单问题就是关于事实的问题，比如某某明星是哪儿人，哪一年生的。这都是一些容易的问题，因为它是事实，有明确答案。&lt;/p&gt;    &lt;p&gt;第二类是复杂问题，这也是大家觉得 ChatGPT 非常惊艳的地方。它能整合信息，回答天为什么是蓝色的，好像它自己有逻辑一样。再有一个，就是问过程的问题，比如说我怎么烤蛋糕，你能不把一步步写下来？今天我们问ChatGPT怎么烤蛋糕，它可以把这个过程给你写得很详细，多少杯水，加多少个鸡蛋，加多少面粉等等，它都可以告诉你。然后你根据它提供的答案，就真能烤出蛋糕，而且烤得可能还挺不错。&lt;/p&gt;    &lt;p&gt;这是大家觉得很了不得的地方。但是你要知道，这件事，在2014年其实计算机已经做到了，而且做得很好。所以，这项技术本身并没有太多神秘的地方。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q5:计算机和人，谁更擅长写作？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;现在，大家热议ChatGPT，还有一个原因就是觉得它能写作。比如说写一个工作简报，这是今天美国人用ChatGPT用得最多的地方。我这周干了1234567，这七件事，哎，你看我就不用自己费劲地写了，我让ChatGPT生成一个，然后再编辑一下子就可以了。&lt;/p&gt;    &lt;p&gt;但是，计算机写作这件事，其实你说难也难，说容易也容易，我可以给你举个例子。&lt;/p&gt;    &lt;p&gt;在2014年我离开Google之后，当时不太做编程了，不过那时候我还有一些计算资源，所以我自己在空闲时间会写一些程序，做着玩。当时呢，我就让计算机写了两首诗，大家可以读一下这两首诗。&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;第一首诗是个五言诗，这是用我的话说，叫做李白风格的一首诗，大家可以读一下。这首诗就是计算机自己写的。实际上，你如果读一读，这个诗里还真有一些李白的这个特点。&lt;/p&gt;    &lt;p&gt;那第二首诗，我也把图片放在下面了，你可以看一下。&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;先说一下，因为古诗都有平仄一说，但是我们现在的读音和当时的读音不一样，所以我们也没去管这个平仄到底合不合古，但是这个我们单从它的内容意境来讲，你读的会觉得很顺畅。&lt;/p&gt;    &lt;p&gt;好，那么话说回来。第一首诗怎么做的？&lt;/p&gt;    &lt;p&gt;其实再简单不过了，你就把李白的诗放到计算机里。李白诗一共1000 多首，也就一万来句话，这个对计算机来讲太简单了。它写的时候，就是把句子分拆开来，拆成两个字、三个字一组，比如“空愁”这是一组，“忆长安”这三个字一组。然后它就去拼刚才我讲的语言模型，算概率，哪个概率最大；拆完了以后，我就跟他提一个要求，说要写一首忆长安的诗，它就排列组合，生成出这个《忆长安》，实际上就是这么拼凑出来的。第二首诗稍微复杂一点。&lt;/p&gt;    &lt;p&gt;但你知道这两个程序我写了多长时间？两天。这说明什么呢？说明你让计算机写出一些还挺像样的东西，其实不是一件很困难的事情，它没有你想得这么神秘，或者说计算机写作本身没有这你想得这么神秘。&lt;/p&gt;    &lt;p&gt;那为什么这两首诗看起来特别好？因为这是唐诗，唐诗的格式是固定的。同样的道理，为什么用ChatGPT写周报写得好？因为周报的格式基本上是拉清单，那也是个固定的格式。包括，如果你读《华尔街日报》中文版，这里头我跟你讲，90%的内容都是计算机写的，只是你不知道。写完了以后人当然要给它一个主题，然后给它写的第一段话写个引子，然后给一个总结，起个标题，这是人要做的。&lt;/p&gt;    &lt;p&gt;为什么写财经文章比较好？因为它有好多的事实在里头，格式也是固定的，所以这件事它做起来就很好。&lt;/p&gt;    &lt;p&gt;我花这么长时间来讲ChatGPT的背景，实际上就是想说它并不神秘，不是一个什么很高深的机器在背后。一方面，ChatGPT依靠的是一个数学模型，而这个数学模型1972年就有了，只是今天它的计算能力非常强，靠蛮力计算。&lt;/p&gt;    &lt;p&gt;那么，ChatGPT训练一次要耗多少电？大概可能是3000辆特斯拉的电动汽车，每辆跑到20万英里，把它跑死，这么大的耗电量，才够训练一次，这个非常花钱的一件事。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q6: ChatGPT对我们到底有什么影响？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;那么接下来讲讲，ChatGPT对人有什么影响。&lt;/p&gt;    &lt;p&gt;这就要回到历史上来看了，每一次技术革命，其实它对人都会有一些影响。不过，ChatGPT它不算是一项新的技术革命，因为这我刚才讲了，这个过程很长，从20世纪70年代到90年代，我们做了很多事，90 年代到现在又有很多人做了很多事。这里头最大进步其实不是这个语言模型本身，实际上是后来2000年左右产生的深度学习，使得训练语言模型能比以前准确了，不是简单的做统计。&lt;/p&gt;    &lt;p&gt;今天训练语言模型早已经不是简单做统计了，这才是ChatGPT能产生比较好的结果的一个原因。&lt;/p&gt;    &lt;p&gt;至于说ChatGPT对人能产生什么样影响，这个问题我先不直接回答你，我先问你，刚才给大家看这两首唐诗，你有没有发现一个什么特点？对了，这两首诗写得不错，但是你原来对唐朝了解，不会因为多了这两首诗会有更新的了解。因为，ChatGPT它某种程度上有点像鹦鹉学舌，你先要说一段话，它才能跟着学。它说出来的声音可能很好听，但是它并不提供更多的信息。&lt;/p&gt;    &lt;p&gt;今天互联网上90%的内容都属于这一类——不提供更多的新信息，也不是原创内容，也不是自己的感悟，无非是东抄抄，西凑凑。目前，抖音、快手这类短视频，我觉得99%的内容都属于这一类，没有营养，你读完以后可能觉得挺有意思，但实际上你在上面读了再多，其实对你没有任何帮助。&lt;/p&gt;    &lt;p&gt;如果说ChatGPT真的威胁到了谁，我觉得威胁到的就是这一类人的工作，就是说这个抖音上头那个做短视频的，或者发布一些内容的，ChatGPT会做得比他们好很多。你就想这样一件事儿，假设说，有一群人天天把那唐诗三百首里头的句子翻来覆去的捯饬，也能捯饬出一些诗，那么ChatGPT捯饬起来肯定比人快得多，所以这项技术会对这一批人会有影响。&lt;/p&gt;    &lt;p&gt;那么，什么人不会受到影响？就是内容创造的人不会受影响。&lt;/p&gt;    &lt;p&gt;为什么我会这么讲？还记得刚才我说的“为什么是天是蓝色的”这个问题吗？Google为什么能回答这个问题？&lt;/p&gt;    &lt;p&gt;因为在Google进行回答的时候，它大概把当时英语几乎所有的像样的句子都做了分析，大概有1000 亿句英语句子。那么实际上你会发现，在一些大学的网站上和NASA的网站上，它就有这个答案，只是我们把它拼拼凑凑，删删减减，就把它挑出来了。但是最早的物理学家做这项研究，把这个道理搞清楚，这个工作是有意义的，也是ChatCPT取代不了的。&lt;/p&gt;    &lt;p&gt;所以，ChatGPT的工作相当于什么呢？举例子，托勒密创造出这个模型以后，那么每过一段时间，他们欧洲就会编一个大概几十年的一个日历，然后上面标上哪天有日食，哪天行星会怎么运动等等。那么人们根据这些规律，印好多本这个书，这个ChatGPT就相当于有好多本书，你拿着以后一看，说，喔，某年月日会发生月食，答案就会很清楚。但是，背后真正有意义的工作不是印这个书，而做托勒密的那个研究。&lt;/p&gt;    &lt;p&gt;所以我认为，从历史上看ChatGPT其实不算是一次技术革命，它影响到的都是那个比较懒的人，懒得动脑筋，创造新东西的人。真正探索人类知识奥秘的人，是不会被取代的。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Q7: ChatGPT能带来什么新的机会？&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;很多人问说，ChatGPT有什么新机会？坦率来讲，你没机会，因为太耗资源了，你耗不起。那么什么人能够受益？那就是卖资源的这些人。&lt;/p&gt;    &lt;p&gt;我可以打个比方，就是说在这个加州淘金热的时候，很多人蜂拥而至，去淘金，单我们到今天为止还不知道哪一个淘金者真的挣得着钱，没一个人把名字留下来。但是最后谁挣着钱了？是卖水的人和卖牛仔裤的人。ChatGPT也是一样的道理。大家跟着一起去淘金，其实你是挣不着钱的，但是在过程中，你还不断地要买水喝，买牛仔裤穿，最后就是这两拨人挣到钱了。李维斯Levi&amp;apos;s，就是那时候产生的这么一个公司，它就是做牛仔裤的。&lt;/p&gt;    &lt;p&gt;那么最后你可能是给几家大的做云计算的公司在交钱，这可能是一个结果。&lt;/p&gt;    &lt;p&gt;好了，讲完了这个ChatGPT的历史，我给你做一个简单的总结。&lt;/p&gt;    &lt;p&gt;第一，不要恐惧。&lt;/p&gt;    &lt;p&gt;今天是很多人恐惧ChatGPT，就如同不要像当年哥伦布遇到的牙买加土著人恐惧月食，一样的道理。&lt;/p&gt;    &lt;p&gt;第二，不要勉强去找所谓的机会，该怎么工作就是怎么工作。&lt;/p&gt;    &lt;p&gt;我看有同学问我，说苹果为什么这个不做ChatGPT，我说这就对了！这就是为什么苹果是世界上最有钱的公司，利润最高，市值最多。目前，很多所谓做这种人工智能的公司到现在都在亏钱。所以，这也是为什么很多同学有时候问很多太不着调的问题的时候，我就开玩笑地问他说，你的房贷还清了吗？你要没还清，你就好好回去工作，把工作做好，这才是对大家最有意义的事情，从历史上看也是如此。&lt;/p&gt;    &lt;p&gt;第三，你要识破这些所谓的阴谋家或者想割你韭菜的人的那些把戏。&lt;/p&gt;    &lt;p&gt;就是说，如果再来一个人假装哥伦布说他是神的代表，然后他能祈祷上天能让这月亮出来，你不要信。所以你需要了解ChatGPT背后的一些科学原理。最简单的一些原理，像今天我讲的这些，你还是需要有所了解。■      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;br /&gt;    &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62730-%E5%90%B4%E5%86%9B-chatgpt-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Mon, 10 Apr 2023 13:58:12 CST</pubDate>
    </item>
    <item>
      <title>OpenAI 发布 GPT-4，有哪些技术上的优化或突破？</title>
      <link>https://itindex.net/detail/62679-openai-gpt-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;在这个历史性的时刻，回答个问题，留下自己作为历史见证人的足迹。&lt;/p&gt; &lt;p&gt;GPT4的技术报告里很明确地指出了三个新的方向：&lt;/p&gt; &lt;p&gt;第一，LLM最前沿研究的封闭化或小圈子化。技术报告里说了，出于竞争以及安全等方面的考虑，未公布模型规模等技术细节。从GPT 2.0的开源，到GPT 3.0的只有论文，再到ChatGPT连论文也没有，直到GPT 4.0的技术报告更像效果评测报告。一个很明显的趋势是，OpenAI做实了CloseAI的名号，之后OpenAI的LLM最前沿研究不会再放出论文。&lt;/p&gt; &lt;p&gt;在这个情形下，其它技术相对领先的公司有两种选择。一种是做更极致的LLM开源化，比如Meta貌似选择了这条道路，这一般是竞争处于劣势的公司作出的合理选择，但是往往相关技术不会是最前沿的技术；另外一种选择是跟进OpenAI，也选择技术封闭化。Google之前算是LLM的第二梯队，但在“微软+OpenAI”的一顿组合拳下，现在局面有点狼狈不堪。GPT 4.0去年8月就做好了，估计现在GPT 5.0正在炼丹过程中，这么长的时间窗口，结果Google都能落到目前这个局面，想想Transformer、CoT等非常关键的一些研究都是自己做出来的，竟沦落至此，不知一众高层作何感想。Google在后面能快速跟进，维持在第二梯队应该问题不大，很可能比第三名技术也领先很多。出于竞争关系考虑，我猜Google大概率会跟进OpenAI走技术封闭的路线，最先进的LLM技术优先用来炼属于自己的丹，而不是写成论文放出来普惠大众尤其是普惠OpenAI。而这很可能导致LLM最前沿研究的封闭化。&lt;/p&gt; &lt;p&gt;从现在算起，国内在经过一阵时间后（要做到ChatGPT的6到7折应该比较快，要追平估计要较长时间），必然被迫进入自主创新的局面。从最近三个月国内的各种情况看，将来会如何？大概率不太乐观。当然，这个关肯定很难，但必须得过，只能祝愿有能力且有初心者尽力加油了。&lt;/p&gt; &lt;p&gt;第二，GPT 4技术报告里提到的LLM模型的“能力预测（Capability Prediction）”是个非常有价值的新研究方向（其实之前也有个别其它资料，我记得看过，但是具体哪篇记不起来了）。用小模型来预测某些参数组合下对应大模型的某种能力，如果预测足够精准，能够极大缩短炼丹周期，同时极大减少试错成本，所以无论理论价值还是实际价值巨大，这个绝对是非常值得认真研究具体技术方法的。&lt;/p&gt; &lt;p&gt;第三，GPT 4开源了一个LLM评测框架，这也是后面LLM技术快速发展非常重要的方向。尤其对于中文，构建实用的中文LLM评测数据和框架具备特别重要的意义，好的LLM评测数据可以快速发现LLM目前存在的短板和改进方向，意义重大，但是很明显目前这块基本处于空白状态。这个对于资源要求其实没那么高，适合很多机构去做，不过确实是个辛苦活。&lt;/p&gt; &lt;p&gt;除了GPT 4技术报告里明确指出的三个方向，因为最近LLM各方面新闻比较多，顺手再写两个其它技术方向。&lt;/p&gt; &lt;p&gt;首先，斯坦福大学最近在Meta的7B 开源LLaMA基础上，加上Self Instruct技术构造的Alpaca，也代表了一个技术方向。如果归纳下，这个方向可以被称为“低成本复现ChatGPT”的方向。所谓Self Instruct，就是采取一定技术手段，不用人工标注Instruct，而是从OpenAI的接口里，好听点叫“蒸馏”出Instruct，也就是不用人标注，而是ChatGPT作为teacher，给你的Instruct打上标注结果。这个把Instruct标注成本直接打到了几百美金的基准，时间成本就更短了。再加上模型7B规模也不大，所以可以看成一种“低成本复现ChatGPT”的技术路线。&lt;/p&gt; &lt;p&gt;我估计国内早就有不少人采取这个技术路线了。毫无疑问，这是一条捷径，但是走捷径有好处有坏处，具体不展开谈了。在追赶ChatGPT的过程中，先把成本打下来去复现ChatGPT到七八成，我个人还是觉得可行也支持的，毕竟穷人有穷人的玩法。当然，追求把模型做小，效果又不怎么往下掉，如果能扎扎实实去做，是非常具有价值的。&lt;/p&gt; &lt;p&gt;另外，具身智能毫无疑问会是LLM下一阶段的重点研究方向。这方面的代表就是前阵子Google放出来的PaLM-E了。目前的GPT 4，我们可以认为人类创造出了一个超级大脑，但还是把它封锁在GPU集群里。而这个超级大脑需要一个身体，GPT 4要和物理世界发生联系、交流和互动，并在物理世界中获得真实的反馈，来学会在真实世界里生存，并根据真实世界的反馈，利用比如强化学习来学习在世界游走的能力。这个肯定是最近的将来最热门的LLM研究方向。&lt;/p&gt; &lt;p&gt;多模态LLM给予了GPT 4以眼睛和耳朵，而具身智能给予GPT 4身体、脚和手。GPT 4和你我发生一些联系，而依托于GPT 4本身强大的学习能力，这个事情估计很快会出现在我们身边。&lt;/p&gt; &lt;p&gt; 如果你细想，其实还有很多其它有前途的方向。我的个人判断是：未来5到10年，将会是AGI最快速发展的黄金十年。如果我们站在未来30年的时间节点，当我们回顾这10年时，我们中一定会有人，想起下面的诗句：“懂得，但为时太晚，他们使太阳在途中悲伤， 也并不温和地走进那个良夜。”&lt;/p&gt;
 &lt;br /&gt; &lt;br /&gt;
来源：知乎 www.zhihu.com &lt;br /&gt;
    
作者： &lt;a href="http://www.zhihu.com/people/zhang-jun-lin-76?utm_campaign=rss&amp;utm_medium=rss&amp;utm_source=rss&amp;utm_content=author"&gt;张俊林&lt;/a&gt; &lt;br /&gt;
            
 &lt;br /&gt;
【知乎日报】千万用户的选择，做朋友圈里的新鲜事分享大牛。
         &lt;a href="http://daily.zhihu.com?utm_source=rssyanwenzi&amp;utm_campaign=tuijian&amp;utm_medium=rssnormal" target="_blank"&gt;点击下载&lt;/a&gt; &lt;br /&gt;
 &lt;br /&gt;
此问题还有  &lt;a href="http://www.zhihu.com/question/589639535/answer/2937928726?utm_campaign=rss&amp;utm_medium=rss&amp;utm_source=rss&amp;utm_content=title" target="_blank"&gt;587 个回答，查看全部。&lt;/a&gt; &lt;br /&gt;
                延伸阅读： &lt;br /&gt;
 &lt;a href="http://www.zhihu.com/question/589743557?utm_campaign=rss&amp;utm_medium=rss&amp;utm_source=rss&amp;utm_content=title" target="_blank"&gt;OpenAI 发布 GPT-4，该产品都有哪些值得关注的亮点？&lt;/a&gt; &lt;br /&gt;
            
 &lt;a href="http://www.zhihu.com/question/589698290?utm_campaign=rss&amp;utm_medium=rss&amp;utm_source=rss&amp;utm_content=title" target="_blank"&gt;OpenAI 发布 GPT-4，有哪些可以辅助我们的技术？&lt;/a&gt; &lt;br /&gt;
            &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62679-openai-gpt-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Wed, 15 Mar 2023 21:09:44 CST</pubDate>
    </item>
    <item>
      <title>ChatGPT的前世今生：OpenAI的技术“执拗”与“豪赌”</title>
      <link>https://itindex.net/detail/62627-chatgpt-%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F-openai</link>
      <description>&lt;p&gt;　　机器之心专栏&lt;/p&gt;
 &lt;p&gt;　　作者：追一科技&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;本文全方位地介绍了 ChatGPT 的能力特征、发展历程以及 OpenAI 一路走来所坚守的技术路线，并对 ChatGPT 出现之后 NLP 领域的范式转换进行了展望，即 ChatGPT 开启‘文本生成 + 指令’的范式。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;　　1、ChatGPT，不再‘愚蠢’的人工智能&lt;/p&gt;
 &lt;p&gt;　　ChatGPT 的相关话题应该算是继 AlphaGo 以来，最出圈的人工智能热点了。简单来说，它是一个可以用自然语言对话的  &lt;a href="https://finance.sina.com.cn/realstock/company/sz300024/nc.shtml" target="_blank"&gt;机器人&lt;/a&gt;，你可以问它任何问题（当然它有可能会答错，但你可以引导并纠正它），它都会以非常流畅、标准的自然语言回答你。不仅如此，它还能回答代码问题、数学问题等等，你可以和它在关于任何问题上聊得不亦乐乎。&lt;/p&gt;
 &lt;p&gt;　　我们可以用一个经典的鸡兔同笼问题来感性地认识一下 ChatGPT 的能力：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/308/w550h558/20230224/8518-15628a5d0c60b1153d8f9adc01adf93c.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;　　从这个回答可以观察到几个特点。首先是对自然语言的理解和转换为数学问题的能力，其次它通过将一个较为复杂的推理问题分步拆解，一步步获得最后的答案。这个能力被业内称作‘思维链’（Chain of thought）。接下来换一下问法，看看它会怎么回答。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/485/w550h735/20230224/beee-1e6a12d87a6e7826fddaf7d5eb8ac78a.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;　　从这个图中可以发现，ChatGPT 对自己所说的内容是有感知的，并且可以给出这么说的原因。另外也可以发现，它确实也会出错（第一次计算耳朵数量错了。此处有个冷知识，鸡是有类似于‘耳朵’一样的功能器官），但可以通过引导的方式让它给出正确的答案，并且会告诉你自己为什么错了。&lt;/p&gt;
 &lt;p&gt;　　如果不事先告诉你这是一个人工智能模型，ChatGPT 给人的感觉确实像一个真正有逻辑思维和语言交流能力的真人。它的出现第一次让大家觉得，人工智能似乎终于能够和人正常交流了，虽然有时候会出错，但在交流的过程中至少没有语言和逻辑上的障碍，它能‘懂’你在说什么，并且按照人类的思维模式和语言规范给你反馈。这种非常智能的体验感，是它突破业界小圈子，给大众带来冲击感的原因。&lt;/p&gt;
 &lt;p&gt;　　这里还希望再次强调这种体验感的问题，因为也许过去业界由于技术的限制，为了完成场景中的特定任务而忽略了这一点。如今 ChatGPT 的出现代表了人工智能不再是过去那种‘有用，但是也挺蠢’的形态了。&lt;/p&gt;
 &lt;p&gt;　　为了更好地理解 ChatGPT 这种非常智能的感觉是怎么产生的，难免要从过去那种‘很蠢’的人工智能说起。准确地说，ChatGPT 背后使用的依然是自然语言处理（NLP）技术，但却打破了原有的范式。&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;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;p&gt;　　这样的任务还有很多，都是从某个细分的侧面去对自然语言进行分析、处理。这样做有很多好处，比如有了这些拆分，就可以从不同的维度来考察一个自然语言处理系统的细分能力；也可以针对某一个细分的项专门做系统或者模型的设计等。从技术的角度来说，将一个复杂的任务（理解并运用自然语言）拆分为很多的简单任务（各种各样的 NLP 任务）确实是一种比较典型的解决复杂问题的路径，这也是目前的主流做法。然而在 ChatGPT 出现之后，以马后炮视角去看，也许在让计算机理解并运用自然语言这条路上，这种拆分并不是最有效的途径。&lt;/p&gt;
 &lt;p&gt;　　因为在单个任务上的优秀表现，并不能代表系统就掌握了自然语言。人对于人工智能体的‘智能感’，是基于对它应用自然语言的整体能力而产生的，这一点在 ChatGPT 上有明显的体现。虽然 OpenAI 没有开放 ChatGPT 的 API 服务，外界还无法测评它在各个细分 NLP 任务上的具体效果，但以过往外界对它的前身 GPT-3、InstructGPT 等模型的测试情况表明，对于某些特定的任务，一个用专门数据精调过的小模型，确实可以获得更好的效果（详细分析请参考《深入理解语言模型的突现能力》）。但这些在单个任务上有更好表现的小模型，并没有引起很大的出圈效应。归根结底，是因为它们只有单一的能力。单个的能力出众并不能代表它们具有了理解和运用自然语言的能力，从而也无法独自在实际应用场景中发挥作用。正因如此，通常在一个真实的应用场景中，都是多个具有单点能力的模块经过人为的设计拼凑而成，这种人为的拼凑方式是过去的人工智能系统让人感觉并不智能的原因之一。&lt;/p&gt;
 &lt;p&gt;　　从人类理解和运用自然语言的视角去看，这个现象其实很好理解。普通人在理解、运用自然语言的时候，并不会在脑内将它拆分为很多步不同的任务，逐个任务进行分析，然后再汇总，这不是人类使用自然语言的方式。就好比一个人，在听到一句话的时候，并不会对它的句法结构、实体内容与关系、情感倾向等这些内容逐一分析，然后拼凑出这句话的含义，人对语言的理解过程是一个整体过程。再进一步，人对这句话的整体理解，会以自然语言的形式，通过回复的方式整体地表现出来。这个过程并不是像人工智能系统那样，拆分单个任务，然后逐一输出情感分析的标签、实体信息的片段、或是别的某个单个任务的结果，然后用这些东西拼凑出回复。&lt;/p&gt;
 &lt;p&gt;　　而以 ChatGPT 为代表，GPT 系列模型所做的事情才真正接近了人类理解和运用语言的能力 —— 直接接收自然语言，然后直接回复自然语言，并保证了语言的流畅性与逻辑性。这是人与人的交流方式，所以大家对它抱以‘很智能’的体验感。也许很多人会认为，如果能做到 ChatGPT 这样当然很好，过去那种对任务的拆分是因为技术的限制不得已而为之。从技术应用的视角来看，这样迂回的方式当然是需要的，这种方法也在很长的一段时间内被采用，并且确实也能够解决很多实际场景中的问题。但如果回顾 GPT 系列模型的发展过程，就会发现 OpenAI‘赌’了另一条路，并且他们‘赌’赢了。&lt;/p&gt;
 &lt;p&gt;　　2、OpenAI 的‘赌局’&lt;/p&gt;
 &lt;p&gt;　　GPT 初代，一切开始的地方&lt;/p&gt;
 &lt;p&gt;　　早在 2018 年，OpenAI 就发布了最初版本的 GPT 模型，从 OpenAI 公开的论文（Improving Language Understanding by Generative Pre-Training）可以了解到，这个模型采用了 12 层的 Transformer Decoder 结构，用了大约 5GB 的无监督文本数据进行语言模型任务的训练。虽然初代 GPT 模型采用的就已经是生成式的预训练（这也是 GPT 名字的由来，Generative Pre-Training，即生成式预训练），但使用的是无监督预训练 + 下游任务微调的范式。这一范式其实并不是什么新的发明，它在 CV（计算机视觉）领域已经有比较广泛的应用，只是由于当年 ELMo 模型的出色表现，把它重新‘介绍’到了 NLP 领域。&lt;/p&gt;
 &lt;p&gt;　　GPT 模型的出现在那一年确实引来了一些业内的关注，但它并不是那一年的 C 位主角。因为就在同年，Google 的 BERT 模型横空出世，以优异的效果吸引了几乎全部的目光（这个景象有点像现在的 ChatGPT，不禁感叹 Google 和 OpenAI 之间的‘恩恩怨怨’真是天道好轮回）。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/774/w550h224/20230224/4f36-4dd9bf6fdcea36d475fa6d8367a77104.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;　　图片来自 BERT 论文，从图示中可以一窥当年 BERT 对标的就是 GPT，并引以为傲地指出双向编码能力。&lt;/p&gt;
 &lt;p&gt;　　BERT 模型虽然也是采用和 GPT 一样的 Transformer 模型结构，但它几乎就是为‘无监督预训练 + 下游任务微调’的范式量身定制的模型。和 GPT 相比，BERT 所使用的掩码语言模型任务（Masked Language Model）虽然让它失去了直接生成文本的能力，但换来的是双向编码的能力，这让模型拥有了更强的文本编码性能，直接的体现则是下游任务效果的大幅提升。而 GPT 为了保留生成文本的能力，只能采用单向编码。&lt;/p&gt;
 &lt;p&gt;　　以当年的眼光来看，BERT 绝对是一个更加优秀的模型。因为既然 BERT 和 GPT 两者都是采用‘预训练 + 微调’的范式，并且下游任务依然是分类、匹配、序列标注等等‘经典’的 NLP 任务形式，那么像 BERT 模型这种更注重特征编码的质量，下游任务选一个合适的损失函数去配合任务做微调，显然比 GPT 这种以文本生成方式去‘迂回地’完成这些任务更加直接。&lt;/p&gt;
 &lt;p&gt;　　从 BERT 模型出来以后，‘无监督训练 + 下游任务微调’的范式便奠定了它的霸主地位，各类沿着 BERT 的思路，琢磨‘如何获得更好的文本特征编码’的方法大量涌现，以至于 GPT 这个以生成式任务为目标的模型显得像一个‘异类’。马后炮地说，如果当时 OpenAI‘顺应大势’，放弃生成式预训练这条路，也许我们要等更长的时间才能见到 ChatGPT 这样的模型。&lt;/p&gt;
 &lt;p&gt;　　GPT-2 带来的希望&lt;/p&gt;
 &lt;p&gt;　　当然，我们现在见到了 ChatGPT，所以 OpenAI 没有放弃生成式预训练的路线。实际上坚持的‘回报’隐约出现在了第二年，也就是 2019 年。OpenAI 发布了有 48 层 Transformer 结构的 GPT-2 模型。在发布的论文（Language Models are Unsupervised Multitask Learners）中，他们发现通过无监督数据配合生成式训练后，GPT 展示出了零样本（zero-shot）的多任务能力。而奇妙的是，这些多任务的能力并不是显式地、人为地加入到训练数据中的。用通俗的话来举个例子，GPT-2 其中一个展示出来的能力是做翻译，但令人吃惊的是，通常专门用来做翻译的模型是需要大量的平行语料（即两种不同语种之间配对的数据）进行监督训练，但 GPT-2 并没有使用这种数据，而仅仅是在大量的语料上进行生成式的训练，然后它就‘突然’会做翻译了。这个发现或多或少地带有点颠覆性的意味，它向人们展示了三个重要的现象：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;想让模型去完成一种 NLP 任务，也许并不需要和任务匹配的标注数据。例如 GPT-2 训练时没有用标注的翻译数据，但它会做翻译；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;想让模型去完成一种 NLP 任务，也许并不需要和任务匹配的训练目标。例如 GPT-2 训练的时候并没有设计翻译任务和相关的损失函数，它只是在做语言模型任务。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;仅仅用语言模型任务（即生成式任务）训练的模型，也可以具有多任务的能力。例如 GPT-2 展现出了翻译、问答、阅读理解等等的能力。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;　　虽然以现在的眼光来看，当时的 GPT-2 所展示出来的各种能力还比较初级，效果距离使用监督数据微调后的一些其它模型还有明显的差距，但这并没有妨碍 OpenAI 对它所蕴含的潜在能力充满期待，以至于在论文摘要中的最后一句话中，他们提出了对 GPT 系列模型未来的期望：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;“These findings suggest a promising path towards building language processing systems which learn to perform tasks from their naturally occurring demonstrations．”&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;　　后来一系列事情的发展也证明了他们确实是一直朝着这个 promising path 的方向在前进。如果说在 2018 年，GPT 初代模型出来的时候，GPT 的生成式预训练还面临着被 BERT 这类以‘提取特征’为目地的预训练模型在各方面碾压，那么在 GPT-2 中的发现，给了生成式预训练一个 BERT 类模型无法替代的潜在优势，即语言模型任务所带来的多任务能力，且这种多任务能力是无需标注数据的。&lt;/p&gt;
 &lt;p&gt;　　当然，在那个时间点上，生成式的技术路径依然面临风险和挑战。毕竟当时的 GPT-2 在各任务上的表现还是差于经过微调的模型，这导致了 GPT-2 虽然有着翻译、摘要等等能力，但效果太差以至于无法实际使用。因此，如果在当时想要一个可用的翻译模型，最好的选择依然是老老实实用标注数据训练一个专门用来翻译的模型。&lt;/p&gt;
 &lt;p&gt;　　GPT-3，数据飞轮的开始&lt;/p&gt;
 &lt;p&gt;　　从 ChatGPT 时代往回看，也许 OpenAI 在 GPT-2 中的发现确实坚定了他们对 GPT 系列模型的信心，并决定加大研发投入力度。因为在随后的 2020 年他们发布了 1750 亿参数量的 GPT-3，一个即便以现在的眼光去看也大得让人惊叹的模型。虽然 OpenAI 没有明确公开训练这个模型的费用，但大家的估算是当时花了 1200 万美元。同时公开的还有一篇长达 60 多页的论文（Language Models are Few-Shot Learners），其中详细阐述了这个新的庞然巨物所展示出来的新能力。最重要的发现莫过于论文标题中所说的，语言模型具有小样本（few-shot）学习的能力。&lt;/p&gt;
 &lt;p&gt;　　小样本学习是一个机器学习领域的专业术语，但它有着很朴素的理念，即‘人类是可以通过少量的几个例子就学会一个新的语言任务’。想象一下在语文课上学习怎么掌握‘把’字句换成‘被’字句样（雨把衣服淋湿了 —— 衣服被雨淋湿了）的情形，老师会给出几个例子，同学们就能够掌握这项能力。&lt;/p&gt;
 &lt;p&gt;　　但对于深度学习模型来说，它通常需要学习（训练）成千上万的例子才能掌握一项新的能力，但大家发现 GPT-3 却像人类一样具有类似的能力。而且重点在于，只需要给它展示几个例子，它就会‘有样学样’地完成例子给出的任务，而不需要进行额外的训练（即不需要进行常规训练中的梯度反传和参数更新）。后来的研究表明，这种能力是巨型模型所特有的，被业内叫做‘在上下文中学习’（in context learning）的能力。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/695/w550h945/20230224/b5b5-187e68ba533ac3d88a258c55f92ec4b1.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　GPT-3 论文中所展示的英语翻译法语的 In context learning 能力。&lt;/p&gt;
 &lt;p&gt;　　实际上，小样本学习能力本身并不是很惊人的发现。毕竟业内一直都在对小样本学习进行研究，很多专攻小样本学习的模型都有出色的小样本学习能力。但 GPT-3 展示出来的这种‘在上下文中学习’的小样本能力却非常出人意料，其原因也和 GPT-2 所展示的多任务能力一样：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;GPT-3 并没有为了获得小样本的能力而在训练数据、训练方式上做特别的设计，它依然只是一个用语言模型任务训练的生成式模型；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;GPT-3 的小样本能力是以‘在上下文中学习’的方式展现出来的。换句话说，想让它获得新的能力，不需要对它再训练，而只需要给它看几个示范的例子。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;　　除了这个能力以外，GPT-3 还展示出了优秀的文本生成能力，相比 GPT-2，它生成的内容更加流畅，而且可以生成很长的内容。这些能力综合体现在一个模型上，让 GPT-3 在当时成为了大家的关注焦点，它也成为 OpenAI 正式对外提供服务的模型。&lt;/p&gt;
 &lt;p&gt;　　但随着这个模型服务的开放，越来越多的人尝试使用这个模型。从这时起，OpenAI 通过开放给公众的方式，同时也在收集着更具有多样性的数据（用户使用时输入的内容可能会被用于模型的训练，这一点是写在用户条款中的），这些数据在后来的模型迭代中也发挥着重要的作用。自此 GPT 系列模型的数据飞轮便转动了起来，越多优质的用户数据，迭代出效果越好的模型。&lt;/p&gt;
 &lt;p&gt;　　与 ChatGPT 不同的是，GTP-3 并不是采用对话的形式交互的模型，而是一个文本的续写模型（也就是在你输入的文字后面接着往下写），因此它并不具备如今的 ChatGPT 所拥有的多轮对话能力。但它已经能够干很多的事情，例如编写故事、给邮件做自动补全等等。但同时，大家也慢慢发现了一些问题，例如它会一本正经地输出不符合事实的内容，并且会输出一些有害的言论等等。这是这种文本生成模型最突出的弊端，虽然经过多次迭代，但 ChatGPT 如今也依然面临类似的问题。&lt;/p&gt;
 &lt;p&gt;　　CodeX，让计算机自己写代码&lt;/p&gt;
 &lt;p&gt;　　OpenAI 在对 GPT-3 的研究中还有一个意外的发现，它能够根据一些注释生成很简单的代码。因此在随后的 2021 年，他们对生成代码这件事情进行了专门的研究投入，并发布了 CodeX 模型。它可以看作是一个有着代码专精能力的 GPT 模型，能够根据自然语言输入生成比较复杂的代码。&lt;/p&gt;
 &lt;p&gt;　　从外部视角来看，我们无法判断代码生成的研究与 GPT 系列模型的研发是否在同时进行。但放在当时，让模型具有生成代码的能力，从实用化的角度来说确实更加具有意义，毕竟 GPT-3 还未拥有如今 ChatGPT 这般强悍的能力。另一方面，让模型去生成代码也能规避它生成有危害文本内容带来的风险。&lt;/p&gt;
 &lt;p&gt;　　在 CodeX 论文中提及了几个要点，首先是让经过文本数据预训练的 GPT 模型在专门的代码数据（数据来自 github 的开源代码，一共 159G）上训练确实能够明显提升模型对代码的理解和输出能力。其次是论文中用的是一个 120 亿参数的‘小’模型，这个信息从侧面反映出 OpenAI 内部除了对外开放接口的 1750 亿参数的 GPT-3 模型外，还有别的不同大小的模型版本。&lt;/p&gt;
 &lt;p&gt;　　而加入代码训练，让模型获得理解和生成代码的决定，原本的初衷也许只是希望 GPT 能够多一种应用场景。它看似与 GPT 系列模型在理解和运用自然语言的能力没有太大的联系，但根据后续的研究（详细的分析请参考文章《拆解追溯 GPT-3.5 各项能力的起源》），增加对代码数据的训练很有可能触发了后来的 GPT 模型在自然语言上的复杂推理和思维链的能力。&lt;/p&gt;
 &lt;p&gt;　　也许在 OpenAI 做 CodeX 之初并没有预料到会有这样的结果，但就像他们一直使用文本生成任务来做 GPT 模型，然后在 GPT-2 和 GPT-3 中‘解锁’了‘多任务的能力’和‘在上下文中学习的能力’那样，代码数据的引入又一次让他们获得了意料之外的收获。虽然看上去似乎有一些偶然，但对技术路线的前瞻性认知，加上坚持与持续的投入显然是一个至关重要的因素。&lt;/p&gt;
 &lt;p&gt;　　InstructGPT，让 GPT 好好说话&lt;/p&gt;
 &lt;p&gt;　　在前面我们提到了 GPT-3 虽然已经有很强的能力，但上线以后随着使用的人越来越多，也发现了很多问题，最严重的应该要数‘一本正经地胡说八道’和‘输出带有危害性的内容’这两点了。虽然在 2021 年 OpenAI 似乎暂时将重点放在了让模型理解和生成代码这件事情上，但他们应该一直在尝试解决 GPT-3 的这些问题。&lt;/p&gt;
 &lt;p&gt;　　在 2022 年初，OpenAI 发表了 InstructGPT 的论文（Training language models to follow instructions with human feedback），从中我们可以一窥解决这些问题的方法。论文的核心理念是让模型接受人类的教导（反馈），这一点在标题中就已经开宗明义了。&lt;/p&gt;
 &lt;p&gt;　　GPT-3 之所以会出现‘一本正经地胡说八道’和‘输出有害的内容’这样的问题，其根源来自于它所使用的训练数据。像 GPT-3 这样的庞然大物，对数据的需求量是海量的。我们从 GPT-3 的论文中可以找到它的数据来源，大致可以划分为三类：网页内容、百科内容以及书籍。虽然网页内容的量非常大，但也非常‘脏、乱、差’，自然会包含很多非真实性和有害的内容。GPT-3 在这些数据上进行训练，自然也就学到了这些东西。&lt;/p&gt;
 &lt;p&gt;　　但作为一款对外提供服务的产品，GPT-3 的回答应该更小心一些。要解决这个问题，其中的一难点在于怎么去定义模型应该怎么说话。因为生成模型的输出内容是自然语言本身，而不是一个分类的标签或一个实体名词这种有明确的、客观对错的内容。没有明确的对错，就导致无法像训练经典的 NLP 模型那样直接针对目标设计训练任务。&lt;/p&gt;
 &lt;p&gt;　　而 InstructGPT 给出的解决思路是非常直接的，既然对于‘好的回答’这个评价指标有很多不同的影响因素，这些因素又相互交织在一起，那就让人来教它怎么写回答。因为人类是比较善于处理这种‘既有明确的要求，又有模糊的范围’的问题的，让真人写一些‘优秀范例’，让模型去学习这些‘优秀范例’，这正是 InstructGPT 提出的总体思路。&lt;/p&gt;
 &lt;p&gt;　　具体而言，InstructGPT 提出了两个阶段的路径来让 GPT 学习人类给出的‘优秀范例’，第一阶段是监督学习，第二阶段是强化学习。在第一阶段中（对应下图中最左边的 Step 1），让真人根据不同的 Prompt（粗浅可以认为是我们使用 ChatGPT 时，在对话框里输入的那条文本，在业界这个东西叫做指令）写真实的、无害的、有用的回答。实际操作过程中，为了保证这些内容的质量，会给写回答的标注人员一些规范性的指引，然后让已经经过预训练的 GPT 模型在这些人类编辑的数据上继续训练。这一阶段可以看作是对模型的一种‘规训’，用一个不严谨的类比来说，就像语文老师让你默写优秀范文那样。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/74/w550h324/20230224/1b3d-f3fe54b65e22dcda9c814771bd0159c4.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　图片来自 InstructGPT 论文，提出通过监督式的指令微调 + 人类反馈的强化学习来让模型的输出变得合理。&lt;/p&gt;
 &lt;p&gt;　　第二阶段是强化学习，技术上分为两步。第一步（对应上图中间的 Step 2）是让被‘规训’后的模型根据不同的 Prompt 生成多个不同的回答，并由人来给这些回答按照好与差的标准来排序。然后用这些标注了优劣之分的数据训练一个打分模型，让它可以自动给更多的数据进行排序打分。强化学习阶段的第二步（对应上图中右边的 Step 3）就是利用这个打分模型作为强化学习中的环境反馈，以策略梯度（Policy Gradient，准确地说是 PPO 算法）的方式对已经‘规训’后的 GPT 模型进行训练。整个第二阶段的过程可以看作是对模型的一种‘强化’，再用一个不严谨的类比来说，就像语文老师给你写的作文打分，让你从分数中分辨什么是好与不好，然后不断进步。&lt;/p&gt;
 &lt;p&gt;　　因此，用一种非常不严谨，但普通人或许也能够理解的方式来说，InstructGPT 先是让一个‘口无遮拦’的 GPT 通过‘默写人类的优秀范文’的方式初步学会‘好好说话’，然后再‘给它独自写出来的东西打个分，让它回去好好领悟，继续进步’。当然，在技术上牵涉事情会更复杂一些，比如‘优秀范文’的具体规范和数量等数据上的问题，以及强化学习中打分模型的选择，算法参数的设置等算法上的问题，都会对最后的效果产生影响。但最终的结果表明，这种方式是非常有效的，论文中也指出一个通过上述方式训练出来的 13 亿的小模型，效果就能够超过没有经过这种训练的更大的模型。&lt;/p&gt;
 &lt;p&gt;　　另外论文中还有一些非常值得一提的内容。首先是关于 Prompt 的一些发现。InstructGPT 训练时所使用的 Prompt 主要由两部分构成，一部分是专门的 AI 训练师编写的，另一部分自来 OpenAI 的模型在线服务期间，由用户使用中编写的内容，这时数据飞轮的作用就体现了。可以发现，无论是哪种，这些 Prompt 都是由真人写出来的，虽然文章中没有对这些 Prompt 的具体涵盖范围、分布情况以及提问的方式展开详细的分析，但可以合理地猜测这些 Prompt 具有一定的多样性和较高的质量。其实文章中对比了使用这些真人编写的 Prompt 训练的模型和使用一些开源 NLP 任务数据集中构建的 Prompt（例如 T0 数据集、FLAN 数据集）训练出来的模型，结论是由真人编写 Prompt 训练出来的模型，给出的答案更加能被评测的人接受。&lt;/p&gt;
 &lt;p&gt;　　另外一点是关于训练好的模型对新的 Prompt 的泛化能力的问题，可想而知的是，如果训练完成的模型无法产生 Prompt 的泛化能力，那么现在 ChatGPT 所表现出来的，几乎百问百答的能力是不太可能产生的。因为在模型做微调的阶段，即便是再多的数据，也不可能把人们有可能会输入的内容都覆盖完整。而 InstrctGPT 论文中点明了文中所采用的方法是可以产生 Prompt 的泛化能力的。&lt;/p&gt;
 &lt;p&gt;　　之所以花了更多的文字对 InstructGPT 做介绍，因为根据 ChatGPT 官方页面的介绍，InstructGPT 中的方法正是用来训练 ChatGPT 的方法，不同的只是 ChatGPT 使用了对话式的数据组织方式。&lt;/p&gt;
 &lt;p&gt;　　GPT-3.5 时代和 ChatGPT 的诞生&lt;/p&gt;
 &lt;p&gt;　　在随后的时间内，OpenAI 发布了多个被称为 GPT-3.5 系列的模型，虽然这些模型并未有相关的论文跟随发表，但根据这篇文章的分析，GPT-3.5 系列应该是融合了 OpenAI 在 GPT-3 时代积累的技术、数据以及经验开发出来的。由于没有详细的官方公开信息参考，关于这些模型的具体资料，外界主要是通过分析使用的体验、相关的技术论文以及 OpenAI 的 API 文档介绍来进行推测。&lt;/p&gt;
 &lt;p&gt;　　根据分析，GPT-3.5 系列的模型有可能并不是在 GPT-3 上继续微调而来，而很可能是将代码和自然语言的数据融合在一起，重新从零开始训练了一个基础模型。这个模型可能比 GPT-3 的 1750 亿参数量更大，它在 OpenAI 的 API 中被命名为 codex-davinci-002。然后在这个基础模型上，通过指令微调和人类反馈得到了一系列后续的模型，包括 ChatGPT。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/136/w550h386/20230224/15e4-98e9ce17ad1561b4bf452b921cc7edd2.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　GPT 系列模型的发展路径。&lt;/p&gt;
 &lt;p&gt;　　简要地说，从 code-davince-002 这个模型开始，经过有监督的指令微调得到 text-davinci-002。以及后续的 text-davinci-003 和 ChatGPT，也是在 GPT-3.5 系列的某个模型上通过指令微调以及人类强化学习反馈得到的。并且 text-davinci-003 和 ChatGPT 都是在 2022 年 11 月发布的，不同的是 text-davinci-003 和 GPT-3 一样，是一个文本补全模型。而根据 ChatGPT 的官方介绍，它是通过将过往的数据处理成对话交互的形式，并增加了新的对话数据训练出来的。&lt;/p&gt;
 &lt;p&gt;　　至此，我们大致回顾了 OpenAI GPT 系列模型从 2018 年的初代 GPT 到现在的 ChatGPT，一路发展迭代的过程。在这个过程中，OpenAI 一直保持着对生成式预训练模型这一技术路径的‘执拗’，并且也一直从不断发展的 NLP 技术中吸纳新的方法，从最初的 Transformer 模型结构，到后来的指令微调（Prompt tuning）等技术的出现，这些因素共同促成了如今 ChatGPT 的成功。有了对 GPT 系列模型发展的了解，我们可以再回过头来看看如今的 ChatGPT。&lt;/p&gt;
 &lt;p&gt;　　3、走近再看 ChatGPT&lt;/p&gt;
 &lt;p&gt;　　在第一章节中我们阐述了 ChatGPT 出圈的原因主要是：‘它以流畅、符合逻辑的自然语言来反馈人类对它输入的自然语言’，从而给与它交流的人带来了很强的‘智能感’。在第二章节中通过回顾 GPT 系列模型的发展历史来了解 ChatGPT 成功之路。而这一章节会尝试以尽可能让圈外人都能理解的方式，稍微深入一些技术的内容，并且探讨一下当前的一些大型文本生成式模型为什么未能做到相同的效果。这一部份的主要参考来自于《深入理解语言模型的突现能力》和《拆解追溯 GPT-3.5 各项能力的起源》这两篇文章以及相关的一些论文，建议希望详细了解细节的读者阅读原文。&lt;/p&gt;
 &lt;p&gt;　　虽然在第一章中指出，ChatGPT 所带来的惊艳效果是由许多不同的 NLP 任务综合体现出来的，但在分析它背后的技术时，还是通过将它的能力进行拆解会更加清晰明了一些。总体而言，ChatGPT 所体现出来的能力可以大致划分为以下几个维度：&lt;/p&gt;
 &lt;p&gt;　　- 文本生成的能力：ChatGPT 的所有输出都是即使生成出来的文本，所以文本生成的能力是它最基本的要求。&lt;/p&gt;
 &lt;p&gt;　　这一项能力实际上是来自于它的训练方式，ChatGPT 在预训练时，是一个标准的自回归语言模型任务，这是 OpenAI 所有 GPT 系列模型的基底。所谓的自回归语言模型任务，通俗的理解是这样的：它可以根据已经输入的文本，预测下一个 token 应该是什么。这里所说的 token，所代表的是模型所使用的最小单位的字符片段，它可以是字（在中文里采用字是很常见的），也可以是词（英文的每个词天然地被空格隔开了，所以常采用词），甚至是字母。但现在的方法通常采用的是子词（subword，介于字母和词之间，主要的目的是减少词表数量）。但不论是哪种，自回归语言模型任务的基本思路都是根据已经输入的文本，预测下一个要输出的文本是什么，就像下图的例子中那样：&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/109/w550h359/20230224/59cd-2c2ffe9caba9dddda64b163cfba56298.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　这个例子中，BOS 代表了输入的开头，而每个 token 是一个词，GPT 模型根据输入的‘今天’和 ‘天气’两个词，预测下一个要输出的是‘不错’。&lt;/p&gt;
 &lt;p&gt;　　在训练的时候，会准备很多文本数据，比如网页上的文章、各类书籍等等，只要是正常的文字内容，都可以用来训练。值得说明的是，这种数据不需要进行额外的人工标注，因为这类数据本来就是人写的，模型要做的事情就是根据这些人写出的文本，去学习‘给定了前面的文字，接着这些文字后面这个地方应该是什么’的问题。这便是业内所称的‘无监督训练’，实际上模型并不是真的没有监督（不然模型学什么呢？），只是它的数据不需要额外的人工标注。也正因为这个任务是不需要额外标注的，因此可以‘免费’获得大量的数据，得益于互联网的普及，可以‘轻松地’获得海量的由真人写出的文本内容用来训练。这一点也是 GPT 系列模型的特点之一，用海量的数据，去训练很大的模型。&lt;/p&gt;
 &lt;p&gt;　　那么在我们使用 ChatGPT 的时候，它是怎么工作的呢？其实也和它的训练方式一样，模型会根据我们在对话框里输入的内容，去预测接在这些内容的下一个 token 是什么，得到这个 token 后，会将它与前面的内容拼接成一个新的文本给模型，模型再预测下一个 token，如此反复，直到满足某个条件后停止。这个停止的条件有很多不同的设计方式，比如可以是输出的文本达到特定的长度，又或者是模型预测出某个用来代表停止的特殊 token。另外值得一提的是，模型预测下一个 token 时，其实背地里是一个采样的过程。换句话说，模型在预测 token 时，输出的其实是所有可能出现的 token 的概率，然后从这个概率分布里采样一个 token。因此，在使用 ChatGPT 时，会发现即便对于相同的输入，它的输出每次也会不一样，因为在背地里它采样了不一样的 token 作为输出。&lt;/p&gt;
 &lt;p&gt;　　了解这些之后，可以再回过头来思考一下模型在学什么。它在学习怎么回答问答吗？又或者说它在学习怎么理解自然语言所蕴含的信息、逻辑、情感？还是说它在学习海量的知识？从训练任务的设计来看，似乎都没有，它只是从海量的文本数据里，学习了‘根据输入的这些文本，一个人类在后面会接着写什么’这件事。但正是这样的模型，在‘进化’到 ChatGPT 时，它却掌握了丰富的知识、复杂的逻辑推理等等，它似乎掌握了一个人类运用语言所需要的几乎所有的能力。这是一件非常神奇的事情，它的‘进化’历程将会在下一章节中做更加深入的介绍。&lt;/p&gt;
 &lt;p&gt;　　- 丰富的知识储备：ChatGPT 能够正确回答非常多的问题，包括历史、文学、数学、物理、编程等等。因为目前版本的 ChatGPT 并没有利用外部知识，因此这些知识的内容是‘储存’在模型内部的。&lt;/p&gt;
 &lt;p&gt;　　ChatGPT 所拥有的丰富知识储备，来自于它的训练数据，以及它足够大的体量，以便学会这些内容。虽然官方并没有公开 ChatGPT 所使用的训练数据的具体细节，但从它的前身 GPT-3 的论文可以推测，这些数据大致可以分为三个大的范畴：网页内容、书籍内容以及百科内容。可想而知的是，这些内容天然地蕴含了大量的知识，百科和书籍自然不必说，而网页内容也包含了许多新闻、评论、观点等，并且网页也包括了很多专门的问答垂直类网站，这些都是 ChatGPT 的知识来源。在官方的介绍里指出 ChatGPT 无法回答 2021 年以后发生的事情，因此合理的猜测是训练的数据收集截止到 2021 年。&lt;/p&gt;
 &lt;p&gt;　　但数据量只是其中一个方面，要让模型‘掌握’这些数据，其自身的体量是不可能小的。以 GPT-3 为例，它有 1750 亿参数，可以粗浅地理解为，这些数据的内容以及模型的各项能力，都以这一个个参数的具体数值的形式，固定在了训练完成的模型中。感性的理解是，假设一个模型只有 1 个参数，那它什么也干不了。更严谨的分析和对比可以参考这篇论文《Holistic Evaluation of Language Models》的测评，方向性的结论是越大的模型，在需要知识来完成的任务上表现得越好。&lt;/p&gt;
 &lt;p&gt;　　论文地址：https：//arxiv.org/pdf/2211.09110.pdf&lt;/p&gt;
 &lt;p&gt;　　- 逻辑推理与思维链的能力：从第一章图片中的鸡兔同笼的例子可以看出，ChatGPT 具有很强的逻辑推理能力。并且它能够将复杂的内容，通过拆解，分成多个小的步骤，一步步地进行推理，获得最后的答案，这种能力被称为思维链。&lt;/p&gt;
 &lt;p&gt;　　从前面的介绍我们知道，模型在训练的时候并没有针对逻辑推理以及思维链做特定的设计。而当前的主流观点认为，逻辑推理和思维链很可能和两个因素相关，第一个是模型的体量，第二个是模型是否在代码数据上进行过训练。&lt;/p&gt;
 &lt;p&gt;　　关于模型体量与推理、思维链能力的关系，在《深入理解语言模型的突现能力》中有对应的介绍。下面这张图展示了思维链能力与模型体量的关系。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/489/w550h739/20230224/750a-cf4ab8cf4c0cfa90bb74a145405131c6.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　不同模型不同尺寸的思维链效果对比，图来自论文。GSM8K，SVAMP 和 MAWPS 是三个需要逻辑推理的数学应用题数据集，LaMDA，GPT 和 PaLM 分别是 3 个不同的模型。&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;li&gt;
   &lt;p&gt;思维链的能力在模型够大的前提下，效果超过了标准的指令（Standard prompting，黑色实线）方法；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;思维链的能力在模型够大的情况下，可以接近甚至超过有监督的方法（橙色虚线）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;　　用通俗的话来说，就是在模型足够大的时候，思维链的能力突然展现了出来，能够达到、甚至超过那些在推理数据集上专门进行有监督训练的模型。这个图也许部分解释了现在我们看到的 ChatGPT 所具有的优异推理和思维链能力。&lt;/p&gt;
 &lt;p&gt;　　而另一个关于推理以及思维链能力的产生因素，与模型是否在代码数据上做过训练有关。目前这一点只是一个推论，《拆解追溯 GPT-3.5 各项能力的起源》中的分析表明体量类似的大型模型，没有在代码上做过训练的话，只有很弱或几乎没有思维链和推理能力。而 ChatGPT 确实是在代码数据上进行过训练的，这一点从它能够理解并生成代码也可以看出来。在第二章回顾发展历程中提到了，OpenAI 在 2021 年就发布了专门针对代码的 CodeX 模型，将代码数据加入 GPT 的训练数据应该就是从那时开始的。&lt;/p&gt;
 &lt;p&gt;　　- 按照人的提问或者指令给予回复的能力：ChatGPT 除了可以用狭义的基于‘问答’形式的交互以外，还能够按照输入的要求进行回复。例如，在应对‘帮我写一封信’这类指令式的要求时，它同样也展现出了优秀的能力。这种能力让它不仅是一个提供答案的‘高级搜索引擎’，更是一种可以用自然语言来交互的文字处理工具。&lt;/p&gt;
 &lt;p&gt;　　虽然目前大众普遍把目光聚焦在将 ChatGPT 作为一种类搜索引擎的工具，但查阅相关知识并给予回答并不是它的唯一能力。实际上，单就 ChatGPT 本身而言，回答知识性的问题并不是它的强项，毕竟它本身的训练数据被定格在了 2021 年。即便用更新的数据去训练，但它终究跟不上时事的变化，因此要将它用作知识性的问答工具，还是需要与搜索引擎等外部知识源做结合，就像现在 Bing 做的一样。&lt;/p&gt;
 &lt;p&gt;　　但换一个角度来看，ChatGPT 像是一个‘语言完备’的文本工具，也就是它能够按照你给它的要求，完成指定的、可以用文本形式表达出来的内容，就像下面这个例子。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/174/w550h424/20230224/9338-7d2cc38183881f3b3eb98fd732c4fda0.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　按照给定的计划内容生成英文邮件进行汇报。&lt;/p&gt;
 &lt;p&gt;　　这里所说的‘语言完备’，指的是运用语言的能力。可以看出上面这个例子里，其实不涉及知识性的内容，因为需要它写的内容已经提供给它了。但要写出这封邮件，涉及到的是运用语言的能力，比如遣词造句、语种切换、邮件格式等等。&lt;/p&gt;
 &lt;p&gt;　　现在我们回过头来，尝试分析它的这种‘按照指令完成任务’的能力是怎么获得的。在学界中，这种指令被称为 prompt，实际上对话中的用户输入、问答中的问题也是一种 prompt，因此可以粗浅地理解为，在聊天框里输入的所有内容都是 prompt。如果了解我们在本章第一节介绍语言模型的内容，那么更严谨一些的说法应该是‘输入给模型的上文’都是 prompt。&lt;/p&gt;
 &lt;p&gt;　　ChatGPT 根据输入的指令（prompt）进行回复的能力，是来自于一种被称为指令微调的模型训练方式（prompt tuning）。其实原理很简单，模型依然还是‘根据输入的内容，预测下一个 token 是什么’，只是在指令微调的阶段，输入的内容被换成了这些事先写好的 prompt，而 prompt 后面需要生成的内容，则是事先写好的答案。因此在这一阶段和一开始所说的无监督自回归语言模型训练，最大的不同在于数据。这里的数据，也就是 prompt 以及对应的回复，都是人写的，换句话说，这一阶段用的是人工标注的数据进行的监督训练。&lt;/p&gt;
 &lt;p&gt;　　提到人工标注的数据，就自然牵涉到了所需要的数据量了，因为每一条标注数据都是需要成本的。如果是不需要标注（就像第一阶段的训练），那么自然有海量的文本数据可供训练，但如果要标注，那到底需要多少这种数据呢？要知道，让标注人员手写一个 prompt，然后再手写一个几百字的、真实详尽的回答，成本是很高的。根据论文《Training language models to follow instructions with human feedback》的介绍，所需要的数据其实不需要太多（相比于无监督阶段所使用的数据来说）。虽然具体到 ChatGPT 到底使用了多少没有确切的信息公开，但可以确定的是在数量级上一定远比用来进行无监督训练的网页、百科和书籍所构成的数据集要小非常多。&lt;/p&gt;
 &lt;p&gt;　　只需要相对少量的人工标注的 prompt 数据就能达到让模型按照指令做出回复的目的，这一点背后其实隐含了一个现象，在学界内被称为 prompt 的泛化能力。可以想象一下，如今全世界都在不停的向 ChatGPT 提问，所提的问题也必定是千奇百怪的，这些问题其实就是一个个的 prompt。但用来对 ChatGPT 进行指令微调的 prompt 肯定不会有这么多，这说明模型在学习到了一定量的 prompt 和相应的答案以后，它能够‘举一反三’地对它没有见过的 prompt 进行回答，这就是 prompt 的泛化能力。文章《拆解追溯 GPT-3.5 各项能力的起源》分析指出，这种泛化能力与在指令微调阶段让模型学习的标注数据数量以及多样性相关。&lt;/p&gt;
 &lt;p&gt;　　此外，用少量的 prompt 数据就能微调出类似于 ChatGPT 这样拥有强大能力的模型，背后还隐含了另一个猜测，即模型所表现出来的各项能力，可能在无监督训练的阶段就已经存在于模型当中了。其实这也很好理解，毕竟相比于无监督的数据，这些人工标注的 prompt 数量太少了，很难想象模型是通过对这些仅有的标注数据学习而产生了各种各样的能力。从这个角度来说，指令微调的过程更多只是让模型学会按一定的规范来进行回复，而它的知识、逻辑等能力是早已存在的。&lt;/p&gt;
 &lt;p&gt;　　- ‘客观公正’的能力：如果对 ChatGPT 询问一些有害或者有争议的问题，可以看到 ChatGPT 的回答都是非常‘小心’的，很像是经过训练的新闻发言人般的回答。虽然它目前依然做得不够好，但这种能力是 OpenAI 敢将它公开作为一款产品使用的核心因素。&lt;/p&gt;
 &lt;p&gt;　　让模型的输出符合人类的价值观是 OpenAI 一直在做的事情。早在 2020 年 GPT-3 的时候，OpenAI 就发现这种通过网上的数据训练出来的模型，会生成带有歧视、危险、争议的内容。作为一个对外提供服务的产品，这些有害的内容显然是不合适的。而现在的 ChatGPT 在这一点上有着明显的改善，让模型做出这种‘行为改变’的主要方法也来自于 InstructGPT 的论文，更确切地说，是通过有监督的指令微调加上人类反馈的强化学习共同完成的，这一点在第二章中也已经做过介绍了。&lt;/p&gt;
 &lt;p&gt;　　通过上述的分析可以发现，从技术方法的角度来说，ChatGPT 相关的内容都是已知的，但为什么当前只有它拥有如此惊艳的表现呢。实际上从 ChatGPT 推出之后，NLP 社区就一直在分析这其中的原因，虽然很多结论是推测性的，但也为同类模型的国产化带来一些启示。&lt;/p&gt;
 &lt;p&gt;　　模型体量的因素&lt;/p&gt;
 &lt;p&gt;　　能力涌现出现的前提是模型体量达到一定的规模，虽然没有具体的指标指引，但从目前的事实情况来看，类似于思维链等比较‘高级’的能力，需要在数百亿参数量以上的模型中才表现得足够优异。&lt;/p&gt;
 &lt;p&gt;　　数据量的因素&lt;/p&gt;
 &lt;p&gt;　　模型的大小不是唯一的因素。DeepMind 在这篇论文《Training Compute-Optimal Large Language Models》提供了一些分析性的结论，指出训练的数据量需要随着模型的体量相应地增加，更确切地说，是模型训练时‘见过的 token’数量，需要随着模型体量增加。&lt;/p&gt;
 &lt;p&gt;　　数据质量的因素&lt;/p&gt;
 &lt;p&gt;　　对于无监督的数据，数据量相对而言并不是很大的障碍，但数据质量往往更加容易被忽视。实际上在 GPT-3 的论文中，就有专门的内容介绍数据的处理工作。为了清洗 GPT-3 的训练数据，OpenAI 专门训练了一个数据过滤模型，来从海量的网页数据中获取更高质量的数据。相比而言，与 GPT-3 体量相当的一些开源模型，例如 Meta 的 Opt 和 BigScience 的 Bloom，似乎没有进行这一步清洗。这也许是这两个开源模型效果劣于 GPT-3 的原因之一。&lt;/p&gt;
 &lt;p&gt;　　此外，数据质量的衡量维度不是单一的，诸如数据的多样性、内容重复度以及数据的分布情况都是需要考虑的因素。例如虽然 GPT-3 所使用的网页、百科、书籍这三大类数据中，网页数据的总量是最多的，但在训练时这三类数据的采样并不是按照实际数据的多寡进行的。&lt;/p&gt;
 &lt;p&gt;　　另外值得一提的是，在指令微调的阶段，采用人工编写指令也许是一个重要的影响因素。InstructGPT 的论文明确指出在测评过程中，采用人工编写的指令训练出来的模型，比采用现有的 NLP 数据集通过模版的方式构建指令训练出来的模型有更好的效果。这也许解释了在 T0、FLAN 等由 NLP 数据集构成的指令数据集训练出来的模型为什么效果会差一些。&lt;/p&gt;
 &lt;p&gt;　　训练过程的影响&lt;/p&gt;
 &lt;p&gt;　　这类巨型模型在训练时通过集群进行训练，同时采用数据并行、模型并行以及 ZeRO 优化器（一种降低训练过程显存占用的方法），这些方式为训练的稳定性引入了更多的变量。如下这篇分析指出甚至模型是否采用 bfloat16 精度都对结果有明显的影响。&lt;/p&gt;
 &lt;p&gt;　　相信了解了上面的这些内容，大家对复刻一个类 ChatGPT 的方式以及会面临的问题会有一个大致的了解。有幸的是 OpenAI 已经证明了这技术路径是能够走通的，ChatGPT 的出现也确实正在改变 NLP 技术的发展走向。&lt;/p&gt;
 &lt;p&gt;　　4、未来的展望&lt;/p&gt;
 &lt;p&gt;　　ChatGPT 从 2022 年 11 月上线以来，引起了极大的关注。相信即便是非专业领域，甚至是与计算机也很少打交道的群体，或多或少地都知道它的存在，这个现象本身就已经反映出它的出现有些不同寻常。圈外的大众更多的是以好奇、惊讶或者惊叹的方式来感性地认识它的出现。而对从业者来说，它的出现更多的是对未来技术走向的思考。&lt;/p&gt;
 &lt;p&gt;　　从技术的角度来说，ChatGPT 的出现标志着 NLP 领域的又一次范式切换。之所以说是‘又’一次，是因为在 2018 年，也就是初代 GPT 发布的那一年，与之同年发布的 BERT 模型以自身优异的表现，开创了 NLP 的‘预训练 + 微调’范式的时代，具体内容在第二章中已经做过介绍了。这里主要介绍由 ChatGPT 开启的‘文本生成 + 指令’的范式。具体来说，就是利用训练好的 ChatGPT 或类似的文本生成模型，通过输入适当的指令（prompt）来完成某一项具体的场景。&lt;/p&gt;
 &lt;p&gt;　　这种范式与此前的 NLP 技术应用有很大的不同。不论是早期的利用 LDA、RNN 等统计模型或很小的深度学习模型的时代，还是后来利用 BERT 等预训练配合微调的时代，技术所提供的能力是相对原子化的，距离实际的应用场景有一定的距离。&lt;/p&gt;
 &lt;p&gt;　　就拿前面举的让 ChatGPT 根据要求写英文邮件的例子，按照此前的做法，可能需要先抽取实体、事件等内容（比如时间、地点、事件等），然后通过模版或是模型形成邮件的样式，再通过一个翻译模型转化为英文。当然如果数据量足够训练端到端模型的情况下，也可以跳过中间的若干步骤。但不论采用哪种方式，要么需要将最终的场景拆解成原子化的 NLP 任务，要么需要对应的标注数据。而对于 ChatGPT 来说，只需要一个合适的指令。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/214/w550h464/20230224/4f04-10accca7d75ff310dc54973c2633fc27.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　三个阶段的 NLP 技术范式。&lt;/p&gt;
 &lt;p&gt;　　这种生成式模型搭配 prompt 的方式，直接略过了中间的各项 NLP 能力组件，用最直接的方式解决应用场景的问题。在这种范式下，完成终端应用的技术路径将不再是用单点 NLP 能力模块通过搭积木的方式组合起来。&lt;/p&gt;
 &lt;p&gt;　　当然，这个过程不是一蹴而就的，也不意味着 NLP 的单点能力变得不重要。从测评的角度来说，每一个单点能力的好坏依然可作为评价模型效果的指标。并且，就某些场景来说单点能力依旧是一个强需求。例如在订票系统中本身就需要针对时间、地点进行提取。但与此前不同的是，ChatGPT 本身也可以完成单点能力，而不需要使用额外的功能模块。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/42/w550h292/20230224/18b6-1a53407631036b05600238b3050c8c30.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　ChatGPT 进行信息提取。&lt;/p&gt;
 &lt;div&gt;
  &lt;div&gt;   &lt;img alt="" src="https://n.sinaimg.cn/default/crawl/787/w550h237/20230224/f3e3-31e1e4dc452232523a26ef9c39be664c.png"&gt;&lt;/img&gt;&lt;/div&gt;
&lt;/div&gt;
 &lt;p&gt;　　ChatGPT 进行情感判断。&lt;/p&gt;
 &lt;p&gt;　　从这个角度来说，可以把 ChatGPT 看作是一个以自然语言作为交互媒介的 NLP 工具。如果说在过去，我们是通过模型 + 数据 + 设计训练任务的方式来完成某项 NLP 能力，那么 ChatGPT 则是通过设计指令来完成这些能力。&lt;/p&gt;
 &lt;p&gt;　　可想而知，ChatGPT 的出现大大降低了 NLP 技术的应用门槛。但它目前还不是全能的。最重要的一点在于它缺乏准确可靠的垂直领域知识，为了让它的回答可靠，最直接的方式是为它提供外部的知识来源，就像微软将 Bing 的搜索结果作为它回答的信息来源那样。&lt;/p&gt;
 &lt;p&gt;　　因此，‘传统’的 NLP 技术并不会就此完全消亡，而是会以‘辅助’的角色，作为目前 ChatGPT 短板的补充，这也许会是未来 NLP 技术应用的新范式。&lt;/p&gt;





























            
            
             &lt;div&gt;
                  &lt;div&gt;   &lt;img src="https://n.sinaimg.cn/finance/cece9e13/20200514/343233024.png"&gt;&lt;/img&gt;&lt;/div&gt;
                  &lt;div&gt;海量资讯、精准解读，尽在新浪财经APP&lt;/div&gt;
            &lt;/div&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/62627-chatgpt-%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F-openai</guid>
      <pubDate>Fri, 24 Feb 2023 13:33:07 CST</pubDate>
    </item>
    <item>
      <title>通向AGI之路：大型语言模型（LLM）技术精要 - 知乎</title>
      <link>https://itindex.net/detail/62585-agi-%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B-llm</link>
      <description>&lt;div&gt;    &lt;div&gt;      &lt;p&gt;ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型（LLM,Large Language Model）效果能好成这样；惊醒是顿悟到我们对LLM的认知及发展理念，距离世界最先进的想法，差得有点远。我属于既惊喜又惊醒的那一批，也是典型的中国人，中国人善于自我反思，于是开始反思，而这篇文章正是反思的结果。&lt;/p&gt;      &lt;p&gt;实话实说，国内在LLM模型相关技术方面，此刻，距离最先进技术的差距进一步加大了。技术领先或技术差距这事情，我觉得要动态地以发展的眼光来看。在Bert出现之后的一到两年间，其实国内在这块的技术追赶速度还是很快的，也提出了一些很好的改进模型，差距拉开的分水岭应该是在 GPT 3.0出来之后，也就是2020年年中左右。在当时，其实只有很少的人觉察到：GPT 3.0它不仅仅是一项具体的技术，其实体现的是LLM应该往何处去的一个发展理念。自此之后，差距拉得越来越远，ChatGPT只是这种发展理念差异的一个自然结果。所以，我个人认为，抛开是否有财力做超大型LLM这个因素，如果单从技术角度看，差距主要来自于对LLM的认知以及未来应往何处去的发展理念的不同。&lt;/p&gt;      &lt;p&gt;国内被国外技术甩得越来越远，这个是事实，不承认也不行。前阵子网上很多人担忧说国内AI现在处于“危急存亡之秋”，我觉得倒也不至于这么严重。君不见，这个世界上，具备这么超前眼光的只有OpenAI一家吗？包括Google在内，其实对于LLM发展理念的理解，明显都落后OpenAI一个身位。现实是OpenAI表现过于优秀，把所有人都甩开了，不仅仅是国内。&lt;/p&gt;      &lt;p&gt;我觉得，OpenAI对LLM在理念及相关技术方面，领先国外的Google、DeepMind大约半年到一年的时间，领先国内大概两年左右的时间。在LLM这个事情上，感觉梯队很明显，Google应该是排在第二位，最能体现Google技术眼光的是PaLM和Pathways，推出时间大概在22年2月到4月间，同一时期，OpenAI推出的却是InstructGPT，从这里就可以看出Google和OpenAI的差距了，至于为何这么说，你看了我后面的正文后大概能理解。DeepMind之前的重心一直在强化学习攻克游戏和AI for science这些方面，切入LLM其实很晚，应该是21年才开始重视这个方向，目前也处于追赶状态。Meta就更不用说了，重心一直不在LLM上，目前感觉也发力开始追赶。这还是目前做得最好的一批机构，尚且如此，更何况国内呢？我觉得情有可原。至于OpenAI关于LLM的理念是什么，我在本文的最后一部分，会谈谈我的认知。&lt;/p&gt;      &lt;p&gt;本文梳理自GPT 3.0出现之后的主流LLM技术，在此之前的主流技术可以参考：&lt;/p&gt;      &lt;a href="https://zhuanlan.zhihu.com/p/254821426" target="_blank"&gt;&lt;/a&gt;      &lt;p&gt;，我相信看完这两篇文章，能够让您对LLM领域的技术脉络，LLM技术发展过程中出现过的不同发展理念，乃至未来可能的发展趋势，有比较清晰的认知。当然，很多地方讲的内容是我个人看法，有很大的主观性，错漏难免，所以还请谨慎参考。&lt;/p&gt;      &lt;p&gt;本文试图回答下面一些问题：ChatGPT是否带来了NLP乃至AI领域的研究范式转换？如果是，那会带来怎样的影响？LLM从海量数据中学到了什么知识？LLM又是如何存取这些知识的？随着LLM规模逐步增大，会带来什么影响？什么是In Context Learning?为什么它是一项很神秘的技术？它和Instruct又是什么关系？LLM具备推理能力吗？思维链CoT又是怎么做的？等等，相信看完，能让您对这些问题有一个答案。&lt;/p&gt;      &lt;p&gt;首先，在谈LLM技术现状前，先宏观地谈下我心目中的研究范式转换问题。这样，我们才能“先见森林，再见树木”，对具体技术为何会是如此变化有个更清晰的认知。&lt;/p&gt;      &lt;h2&gt;潮流之巅：NLP研究范式的转换&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;如果我们把时间线往前拉得更长一些，回到NLP领域的深度学习时代，在更长时间窗口内观察技术变迁及其影响，可能会更容易看清其中的一些关键节点。我个人认为，在最近10年来NLP领域的技术发展过程中，可能存在两次大的研究范型转换。&lt;/p&gt;      &lt;h3&gt;范式转换1.0:从深度学习到两阶段预训练模型&lt;/h3&gt;      &lt;p&gt;这个范式转换所涵盖的时间范围，大致在深度学习引入NLP领域（2013年左右），到GPT 3.0出现之前（2020年5月左右）。&lt;/p&gt;      &lt;p&gt;在Bert和GPT模型出现之前，NLP领域流行的技术是深度学习模型，而NLP领域的深度学习，主要依托于以下几项关键技术：以大量的改进LSTM模型及少量的改进CNN模型作为典型的特征抽取器；以Sequence to Sequence（或叫encoder-decoder亦可）+Attention作为各种具体任务典型的总体技术框架。&lt;/p&gt;      &lt;p&gt;在这些核心技术加持下，NLP领域深度学习的主要研究目标，如果归纳一下，是如何有效增加模型层深或模型参数容量。就是说，怎么才能往encoder和decoder里不断叠加更深的LSTM或CNN层，来达成增加层深和模型容量的目标。这种努力，尽管确实不断增加了模型层深，但是从解决具体任务的效果角度看，总体而言，不算很成功，或者说和非深度学习方法相对，带来的优势不算大。&lt;/p&gt;      &lt;p&gt;深度学习之所以不够成功，我认为主要原因来自于两个方面：一方面是某个具体任务有限的训练数据总量。随着模型容量的增加，需要靠更大量的训练数据来支撑，否则即使你能把深度做起来，任务效果也做不上去。而在预训练模型出现之前，很明显这是NLP研究领域一个严重问题；另外一个方面是LSTM／CNN特征抽取器，表达能力不够强。意思是就算给你再多的数据也没用，因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因，阻碍了深度学习在NLP领域的成功突围。&lt;/p&gt;      &lt;p&gt;Bert/GPT这两个预训练模型的出现，无论在学术研究角度看，还是工业应用角度来看，都代表了NLP领域的一个技术飞跃，并带来了整个领域研究范式的转换。这种范式转换带来的影响，体现在两个方面：首先，是部分NLP研究子领域的衰退乃至逐步消亡；其次，NLP不同子领域的技术方法和技术框架日趋统一，在Bert出现后一年左右，技术栈基本收敛到两种技术模式中。关于这两点，我们分头来谈。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响一：中间任务的消亡&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;NLP是一个宏观研究领域的统称，里面有五花八门具体的子领域与子方向，如果仔细分析，从任务的性质角度，可以把这些任务分成两大类：一类可以叫做“中间任务”，一类可以称为“最终任务”。&lt;/p&gt;      &lt;p&gt;典型的中间任务包括：中文分词、词性标注、NER、句法分析、指代消解、语义Parser等，这类任务一般并不解决应用中的实际需求，大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的，比如几乎没有需求说，我要一个句法Parser，把这个句子的句法分析树给用户看看，用户不需要看到这些NLP的中间阶段处理结果，他只关心某个具体任务你有没有干好。“最终任务”包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等，有很多。这类任务的特点是每个子领域都解决某个实际需求，任务结果基本能直接呈现给用户，比如用户确实存在给你一句英文，告诉他中文是什么的需求。&lt;/p&gt;      &lt;p&gt;按理说，“中间任务”就不应该出现，而之所以会存在，这是NLP技术发展水平不够高的一种体现。在技术发展早期阶段，因为当时的技术相对落后，很难一步做好有难度的最终任务。比如机器翻译，早期技术要做好机器翻译是很困难的，于是科研人员就把难题分而治之，分解成分词、词性标注、句法分析等各种中间阶段，先把每个中间阶段做好，然后再拼起来完成最终任务，这也是没办法的事情。&lt;/p&gt;      &lt;p&gt;但是自从Bert／GPT出现之后，其实就没有必要做这些中间任务了，因为通过大量数据的预训练，Bert／GPT已经把这些中间任务作为语言学特征，吸收到了Transformer的参数里，此时我们完全可以端到端地直接解决那些最终任务，而无须对这种中间过程专门建模。这里可能争议最大的是中文分词，其实道理也是一样的，哪些字应该组成一个词，这个其实你不用管，让LLM自己当特征去学就行了，只要对于解决任务有帮助，它自然会去学该学的合理分词方式，也未必一定要和我们人类理解的分词规则相同。&lt;/p&gt;      &lt;p&gt;基于以上认知，其实在Bert/GPT一出现，你就应该得出这类NLP的中间阶段的任务，会逐步退出历史舞台这个结论。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响二：不同研究方向技术路线的统一&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;在说明具体影响前，我们先讨论下另外一种NLP任务划分方式，这对于理解后面内容有帮助。如果对“最终任务”进一步进行分类，又大致可以分为两大不同类型的任务：自然语言理解类任务和自然语言生成类任务。如果排除掉“中间任务”的话，典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等，这种任务本质上都是分类任务，就是说输入一个句子（文章），或者两个句子，模型参考所有输入内容，最后给出属于哪个类别的判断。自然语言生成也包含很多NLP研究子方向，比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本，对应地，模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上&lt;/p&gt;      &lt;p&gt;自从Bert/GPT模型诞生后，出现了明显的技术统一趋向。首先，NLP中不同的子领域，其特征抽取器都逐渐从LSTM/CNN统一到Transformer上。其实，自Bert公开后不久，就应该意识到，这必然会成为技术趋势。至于其原因，在几年前我写的这篇：“&lt;/p&gt;      &lt;a href="https://zhuanlan.zhihu.com/p/54743941" target="_blank"&gt;&lt;/a&gt;      &lt;p&gt;” 中做了说明和分析，感兴趣的同学可参考。而且，目前Transformer不仅统一了NLP诸多领域，也正在逐步地替换图像处理各种任务中被广泛使用的CNN等其它模型的进程之中，类似的，多模态模型目前也基本都采用了Transformer模型。这种Transformer从NLP出发，攻城略地逐步统一AI越来越多领域的趋势，起始于2020年底出现的Vision Transformer (ViT) ，之后蓬勃发展，到目前已大获成功，且其继续向更多领域拓展的势头会越来越迅猛。&lt;/p&gt;      &lt;p&gt;其次，大多数NLP子领域的研发模式切换到了两阶段模式：模型预训练阶段+应用微调（Fine-tuning）或应用Zero／Few Shot Prompt模式。更准确地说，NLP各种任务其实收敛到了两个不同的预训练模型框架里：对于自然语言理解类任务，其技术体系统一到了以Bert为代表的“双向语言模型预训练+应用Fine-tuning”模式；而对于自然语言生成类任务，其技术体系则统一到了以GPT 2.0为代表的“自回归语言模型（即从左到右单向语言模型）+Zero /Few Shot Prompt”模式。至于为何会分化成两条技术路线，有其必然性，关于这点我们放在后面解释。&lt;/p&gt;      &lt;p&gt;这两种模式，看似比较相像，但其背后蕴含了迥异的发展思路，也会导向不同的未来发展方向。不过遗憾的是，我们中的绝大多数人，在当时都低估了GPT 这条发展路线的潜力，而把视觉中心聚焦到了Bert这种模式上。&lt;/p&gt;      &lt;h3&gt;        &lt;strong&gt;范式转换2.0: 从预训练模型走向通用人工智能 （AGI，Artificial General Intelligence）&lt;/strong&gt;&lt;/h3&gt;      &lt;p&gt;这个范式转换所涵盖的时间范围，大致在GPT3.0出现之后（20年6月左右），一直到目前为止，我们应该正处于这个范式转换过程中。&lt;/p&gt;      &lt;p&gt;ChatGPT是触发这次范型转换的关键节点，但是在InstructGPT出现之前，其实LLM处于这次范式转换前的一个过渡期。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;过渡期：以GPT 3.0为代表的“自回归语言模型+Prompting”模式占据统治地位&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;前面说过，在预训练模型发展的早期，技术框架收敛到了Bert模式和GPT模式这两种不同的技术范型，而且人们普遍更看好Bert模式一些，相当多数的后续技术改进，都是沿着Bert那条路走的。但是，随着技术的继续发展，你会发现，目前规模最大的LLM模型，几乎清一色都是类似GPT 3.0这种“自回归语言模型+Prompting”模式的，比如GPT 3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA等，没有例外。为什么会这样呢？背后一定有其必然性，我认为可能主要源于两个原因。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2638' height='1116'&gt;&lt;/svg&gt;" width="2638"&gt;&lt;/img&gt;      &lt;p&gt;首先，Google的T5模型，在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示，标为红色的是个文本分类问题，黄色的是判断句子相似性的回归或分类问题，这都是典型的自然语言理解问题。在T5模型里，这些自然语言理解问题在输入输出形式上和生成问题保持了一致，也就是说，可以把分类问题转换成让LLM模型生成对应类别的字符串，这样理解和生成任务在表现形式就实现了完全的统一。&lt;/p&gt;      &lt;p&gt;这说明自然语言生成任务，在表现形式上可以兼容自然语言理解任务，若反过来，则很难做到这一点。这样的好处是：同一个LLM生成模型，可以解决几乎所有NLP问题。而如果仍然采取Bert模式，则这个LLM模型无法很好处理生成任务。既然这样，我们当然倾向于使用生成模型，这是一个原因。&lt;/p&gt;      &lt;p&gt;第二个原因，如果想要以零示例提示语（zero shot prompting）或少数示例提示语（few shot prompting）的方式做好任务，则必须要采取GPT模式。现在已有研究（参考：On the Role of Bidirectionality in Language Model Pre-Training）证明：如果是以fine-tuning方式解决下游任务，Bert模式的效果优于GPT模式；若是以zero shot/few shot prompting这种模式解决下游任务，则GPT模式效果要优于Bert模式。这说明了，生成模型更容易做好zero shot/few shot prompting方式的任务，而Bert模式以这种方式做任务，是天然有劣势的。这是第二个原因。&lt;/p&gt;      &lt;p&gt;但是问题来了：为什么我们要追求zero shot/few shot prompting这种方式来做任务呢？要解释清楚这个问题，我们首先需要搞清楚另外一个问题：什么样的LLM模型，对我们是最理想的？&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2204' height='1462'&gt;&lt;/svg&gt;" width="2204"&gt;&lt;/img&gt;      &lt;p&gt;上图展示了一个理想的LLM该有的样子。首先，LLM应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它，它应该能够自动从中学习到里面包含的所有知识点，学习过程不需要人的介入，并且能灵活应用所学知识，来解决实际问题。因为数据是海量的，要吸收所有知识，就要非常多的模型参数来存储知识，所以这个模型必然会是一个巨无霸模型。&lt;/p&gt;      &lt;p&gt;其次，LLM应该能解决NLP任何子领域的问题，而不仅支持有限领域，甚至它应该可以响应NLP之外其它领域的问题，最好是任意领域的问题都能得到很好地回答。&lt;/p&gt;      &lt;p&gt;再者，当我们使用LLM解决某个具体领域问题的时候，应该用我们人类习惯的表达方式，就是说LLM应该理解人类的命令。这体现出让LLM适配人，而不是反过来，让人去适配LLM模型。人适配LLM的典型例子，比如绞尽脑汁去尝试各种不同的prompt，以试图找到好的提示语，才能很好地解决手头问题。关于这点，上图在人类和LLM交互的接口层，举了几个例子，说明什么是好的人使用LLM模型的接口形式。&lt;/p&gt;      &lt;p&gt;看完这个理想中的LLM，我们再回头解释上面遗留的问题：为什么我们要追求zero shot/few shot prompting这种方式来做任务呢？有两个原因。&lt;/p&gt;      &lt;p&gt;第一，这个LLM模型规模必然非常巨大，有能力作出这个模型，或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人，就算你把模型开源出来，他们也无力部署这个模型，更不用说再用Fine-tuning这种模式去修改模型参数了。所以，我们应该追求不修正模型参数，就能让任务需求方完成任务的方式，也就是应该采取prompt模式完成任务，而非Fine-tuning模式（由此可看出，soft prompting技术方向是违背这个发展趋势的）。模型制作方则将LLM作成公用服务，以LLM as Service的模式运行。作为服务支持方，考虑到千变万化的用户需求，所以LLM模型制作方更要追求让LLM能完成尽可能多类型的任务，这是附带的影响，也是为何超级大模型一定会追求走向AGI的现实因素。&lt;/p&gt;      &lt;p&gt;第二，zero shot prompting也好，few shot prompting也好，甚至促进LLM推理能力的思维链（CoT,Chain of Thought）Prompting也好，就是上图中接口层中的现有技术。具体而言，zero shot prompting的初衷，其实就是人类和LLM的理想接口，直接用人类所习惯的任务表述方式让LLM做事情，但是发现LLM并不能很好地理解，效果也不好。经过继续研究，转而发现：对于某项任务，如果给LLM几个示例，用这些示例来代表任务描述，效果会比zero shot prompting好，于是大家都去研究更好的few shot prompting技术。可以理解为，本来我们希望LLM能够用人类常用的命令方式来执行某个任务，但是目前技术还做不到，所以退而求其次，用这些替代技术来表达人类的任务需求。&lt;/p&gt;      &lt;p&gt;如果理解了上述逻辑，很容易得出如下结论：few shot prompting（也被称为In Context Learning）只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务，而且LLM可以理解，那么，我们肯定会毫不犹豫地抛弃这些过渡期的技术，原因很明显，用这些方法来描述任务需求，并不符合人类的使用习惯。&lt;/p&gt;      &lt;p&gt;这也是为何我将GPT 3.0+Prompting列为过渡期技术的原因，ChatGPT的出现，改变了这个现状，用Instruct取代了Prompting，由此带来新的技术范式转换，并产生若干后续影响。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响一：让LLM适配人的新型交互接口&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;在理想LLM的背景下，我们再来看ChatGPT，能更好理解它的技术贡献。ChatGPT应该是目前所有的现有技术里，最接近理想LLM的技术方法。如果归纳下ChatGPT最突出特点的话，我会用下面八个字：“能力强大，善解人意”。&lt;/p&gt;      &lt;p&gt;“能力强大”这一点，我相信应该主要归功于ChatGPT所依托的基础LLM GPT3.5。因为ChatGPT 尽管加入了人工标注数据，但是量级只有数万，这个规模的数据量，和训练GPT 3.5模型使用的几千亿token级别的数据量相比，包含的世界知识（数据中包含的事实与常识）可谓沧海一粟，几可忽略，基本不会对增强GPT 3.5的基础能力发挥什么作用。所以它的强大功能，应该主要来自于隐藏在背后的GPT 3.5。GPT 3.5对标理想LLM模型中的那个巨无霸模型。&lt;/p&gt;      &lt;p&gt;那么，ChatGPT向GPT 3.5模型注入新知识了吗？应该是注入了，这些知识就包含在几万人工标注数据里，不过注入的不是世界知识，而是人类偏好知识。所谓“人类偏好”，包含几方面的含义：首先，是人类表达一个任务的习惯说法。比如，人习惯说：“把下面句子从中文翻译成英文”，以此表达一个“机器翻译”的需求，但是LLM又不是人，它怎么会理解这句话到底是什么意思呢？你得想办法让LLM理解这句命令的含义，并正确执行。所以，ChatGPT通过人工标注数据，向GPT 3.5注入了这类知识，方便LLM理解人的命令，这是它“善解人意”的关键。其次，对于什么是好的回答，什么是不好的回答，人类有自己的标准，例如比较详细的回答是好的，带有歧视内容的回答是不好的，诸如此类。这是人类自身对回答质量好坏的偏好。人通过Reward Model反馈给LLM的数据里，包含这类信息。总体而言，ChatGPT把人类偏好知识注入GPT 3.5，以此来获得一个听得懂人话、也比较礼貌的LLM。&lt;/p&gt;      &lt;p&gt;可以看出，ChatGPT的最大贡献在于：基本实现了理想LLM的接口层，让LLM适配人的习惯命令表达方式，而不是反过来让人去适配LLM，绞尽脑汁地想出一个能Work的命令（这就是instruct技术出来之前，prompt技术在做的事情），而这增加了LLM的易用性和用户体验。是InstructGPT/ChatGPT首先意识到这个问题，并给出了很好的解决方案，这也是它最大的技术贡献。相对之前的few shot prompting，它是一种更符合人类表达习惯的人和LLM进行交互的人机接口技术。&lt;/p&gt;      &lt;p&gt;而这必将启发后续的LLM模型，继续在易用人机接口方面做进一步的工作，让LLM更听话。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响二：很多NLP子领域不再具备独立研究价值&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;就NLP领域而言，这次范式转换，意味着很多目前独立存在的NLP研究领域，将被纳入LLM的技术体系，进而不再独立存在，逐步消失。经过第一次范式转换，尽管NLP中很多“中间任务”，继续作为独立研究领域存在不再必要，但是大多数“最终任务”，仍然是以独立研究领域存在的，只是切换成在“预训练+fine-tuning”框架下，面对领域独有问题，陆续提出新的改进方案。&lt;/p&gt;      &lt;p&gt;目前研究表明，很多NLP任务，随着LLM模型规模增长，效果会大幅提升。据此，我觉得可得到如下推论：大多数某领域所谓“独有”的问题，大概率只是缺乏领域知识导致的一种外在表象，只要领域知识足够多，这个所谓领域独有的问题，就可以被很好地解决掉，其实并不需要专门针对某个具体领域问题，冥思苦想去提出专用解决方案。也许AGI的真相超乎意料地简单：你只要把这个领域更多的数据交给LLM，让它自己学习更多知识即可。&lt;/p&gt;      &lt;p&gt;在这个背景下，同时，ChatGPT证明了我们现在是可以直接去追求理想LLM模型的，那么，未来的技术发展趋势应该是：追求规模越来越大的LLM模型，通过增加预训练数据的多样性，来涵盖越来越多的领域，LLM自主从领域数据中通过预训练过程学习领域知识，随着模型规模不断增大，很多问题随之得到解决。研究重心会投入到如何构建这个理想LLM模型，而非去解决某个领域的具体问题。这样，越来越多NLP的子领域会被纳入LLM的技术体系，进而逐步消失。&lt;/p&gt;      &lt;p&gt;我认为，判断某个具体领域是否该立即停止独立研究，其判断标准可采取以下两种方法，占其一即可：第一，判断某个任务，是否LLM的研究效果超过人类表现，对于那些LLM效果超过人类的研究领域，已无独立研究的必要。举个例子，GLUE与SuperGLUE测试集合里的很多任务，目前LLM效果已超过人类表现，与这个数据集合密切相关的研究领域，其实就没有继续独立存在的必要。第二，对比两种模式的任务效果，第一种模式是用较大的领域专用数据进行Fine-tuning，第二种是few-shot prompting或instruct-based方法。如果第二种方法效果达到或超过第一种方法，则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看，其实很多研究领域，目前fine-tuning效果还是占优的（因为这种模式领域训练数据量大），看似还可独立存在。但是考虑到很多任务随着模型规模增大，few shot prompting效果持续增长，随着更大模型的出现，这个拐点很可能短期就会达到。&lt;/p&gt;      &lt;p&gt;如果上述猜测成立，将意味着如下残酷事实：对于很多NLP领域的研究人员，将面临往何处去的选择，是继续做领域独有问题呢？还是放弃这种看似前途不大的方式，转而去建设更好的LLM？如果选择转向去建设LLM，又有哪些机构有能力、有条件去做这个事情呢？你对这个问题的回答会是什么呢？&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响三：更多NLP之外的研究领域将被纳入LLM技术体系&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;如果站在AGI的视角，参照之前描述的理想LLM模型，它所能完成的任务，不应局限于NLP领域，或某一两个学科领域，理想中的LLM应该是领域无关的通用人工智能模型，它现在在某一两个领域做得好，不代表只能做这些任务。ChatGPT的出现，证明了现在这个时期，我们去追求AGI是有可行性的，而现在是抛开“领域学科”这个思维束缚的时候了。&lt;/p&gt;      &lt;p&gt;ChatGPT除了展示出以流畅的对话形式解决各种NLP任务外，也具备强大的代码能力。很自然的，之后越来越多其它的研究领域，也会被逐步纳入LLM体系中，成为通用人工智能的一部分。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2164' height='1416'&gt;&lt;/svg&gt;" width="2164"&gt;&lt;/img&gt;      &lt;p&gt;LLM从NLP向外进行领域拓展，一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入，让LLM成为一个支持多模态输入输出的通用人机接口，典型的例子包括DeepMind的Flamingo和微软的“Language Models are General-Purpose Interfaces”，上图展示了这种方式的概念结构。&lt;/p&gt;      &lt;p&gt;我的判断是无论是图像还是多模态，未来被融入LLM成为好用的功能，可能比我们想象的进度要慢。主要原因在于：尽管图像领域最近两年也一直在模仿Bert预训练的路子，尝试引入自监督学习，释放模型自主从图像数据中学习知识的能力，典型技术就是“对比学习”和MAE，这是两条不同的技术路线。然而，从目前效果来看，尽管取得了很大的技术进步，但貌似这条路尚未走通，这体现在图像领域预训练模型应用到下游任务，带来的效果收益，远不如Bert或GPT应用在NLP下游任务那样显著。所以，图像预处理模型仍需深入探索，以释放图像数据的潜力，而这会迟滞它们被统一到LLM大模型的时间。当然，如果哪天这条路被趟通，大概率会复现NLP领域目前的局面，就是图像处理各个研究子领域可能会逐步消失，被融入到大型LLM中来，直接完成终端任务。&lt;/p&gt;      &lt;p&gt;除了图像与多模态，很明显，其它领域也会逐渐被纳入到理想LLM中来，这个方向方兴未艾，是具备高价值的研究主题。&lt;/p&gt;      &lt;p&gt;        &lt;br /&gt;&lt;/p&gt;      &lt;p&gt;以上是我对范式转换的个人思考，接下来，我们来梳理下GPT 3.0之后LLM模型的主流技术进展。如理想LLM模型所示，相关的技术其实可以分为两大类；一类是关于LLM模型如何从数据中吸收知识，也包括模型规模增长对LLM吸收知识能力带来的影响；第二类是关于人如何使用LLM内在能力来解决任务的人机接口，包括In Context Learning和Instruct两种模式。思维链（CoT）prompting这种LLM推理技术，本质上也属于In Context Learning，因为比较重要，我就把它们单独拎出来讲一下。&lt;/p&gt;      &lt;h2&gt;学习者：从无尽数据到海量知识&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;从目前研究结果看，Transformer是足够强大的特征抽取器，尚不需要做特别的改进。那么通过预训练过程，Transformer学到了什么？知识是如何存取的？我们又如何修正错误知识？本节讲述这方面的研究进展。&lt;/p&gt;      &lt;h3&gt;求知之路：LLM学到了什么知识&lt;/h3&gt;      &lt;p&gt;LLM从海量自由文本中学习了大量知识，如果把这些知识做粗略分类的话，可以分为语言类知识和世界知识两大类。&lt;/p&gt;      &lt;p&gt;语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于LLM能否捕获语言知识有较长研究历史，自从Bert出现以来就不断有相关研究，很早就有结论，各种实验充分证明LLM可以学习各种层次类型的语言学知识，这也是为何使用预训练模型后，各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外，各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在Transformer的低层和中层，而抽象的语言知识比如语义类知识，广泛分布在Transformer的中层和高层结构中。&lt;/p&gt;      &lt;p&gt;世界知识指的是在这个世界上发生的一些真实事件（事实型知识，Factual Knowledge），以及一些常识性知识(Common Sense Knowledge)。比如“拜登是现任美国总统”、“拜登是美国人”、“乌克兰总统泽连斯基与美国总统拜登举行会晤”，这些都是和拜登相关的事实类知识；而“人有两只眼睛”、“太阳从东方升起”这些属于常识性知识。关于LLM模型能否学习世界知识的研究也有很多，结论也比较一致：LLM确实从训练数据中吸收了大量世界知识，而这类知识主要分布在Transformer的中层和高层，尤其聚集在中层。而且，随着Transformer模型层深增加，能够学习到的知识数量逐渐以指数级增加（可参考：BERTnesia: Investigating the capture and forgetting of knowledge in BERT）。其实，你把LLM看作是一种以模型参数体现的隐式知识图谱，如果这么理解，我认为是一点问题也没有的。&lt;/p&gt;      &lt;p&gt;“When Do You Need Billions of Words of Pre-training Data?”这篇文章研究了预训练模型学习到的知识量与训练数据量的关系，它的结论是：对于Bert类型的语言模型来说，只用1000万到1亿单词的语料，就能学好句法语义等语言学知识，但是要学习事实类知识，则要更多的训练数据。这个结论其实也是在意料中的，毕竟语言学知识相对有限且静态，而事实类知识则数量巨大，且处于不断变化过程中。而目前研究证明了随着增加训练数据量，预训练模型在各种下游任务中效果越好，这说明了从增量的训练数据中学到的更主要是世界知识。&lt;/p&gt;      &lt;h3&gt;记忆之地：LLM如何存取知识&lt;/h3&gt;      &lt;p&gt;由上可知，LLM确实从数据中学到了很多语言类及世界知识。那么，对于某条具体的知识，LLM把它存储到了哪里？又是如何提取出来的？这也是一个有意思的问题。&lt;/p&gt;      &lt;p&gt;显然，知识一定存储在Transformer的模型参数里。从Transformer的结构看，模型参数由两部分构成：多头注意力（MHA）部分占了大约参数总体的三分之一，三分之二的参数集中在FFN结构中。MHA主要用于计算单词或知识间的相关强度，并对全局信息进行集成，更可能是在建立知识之间的联系，大概率不会存储具体知识点，那么很容易推论出LLM模型的知识主体是存储在Transformer的FFN结构里。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2828' height='1496'&gt;&lt;/svg&gt;" width="2828"&gt;&lt;/img&gt;      &lt;p&gt;但这样的定位，粒度还是太粗，无法很好回答具体某条知识是如何存储与提取的，比如 “中国的首都是北京”这条知识，以三元组表达就是&amp;lt;北京，is-capital-of，中国&amp;gt;，其中“is-capital-of”代表实体间关系。这条知识它存储在LLM的哪里呢？&lt;/p&gt;      &lt;p&gt;“Transformer Feed-Forward Layers Are Key-Value Memories”给出了一个比较新颖的观察视角，它把Transformer的FFN看成存储大量具体知识的Key-Value存储器。如上图所示（图左是原始论文图，其实不太好理解，可以看做了注释的图右，更好理解些），FFN的第一层是个MLP宽隐层，这是Key层；第二层是MLP窄隐层，是Value层。FFN的输入层其实是某个单词对应的MHA的输出结果Embedding，也就是通过Self Attention，将整个句子有关的输入上下文集成到一起的Embedding，代表了整个输入句子的整体信息。&lt;/p&gt;      &lt;p&gt;Key层的每个神经元节点，记载了一对&amp;lt;Key,Value&amp;gt;信息。比如对于上图中FFN第一个隐层的第i个节点k_{i}，也许就是它记载了&amp;lt;北京，is-capital-of，中国&amp;gt;这条知识。k_{i}节点对应的key向量，其实指的是节点k_{i}和输入层每个节点的权重向量；而对应的Value向量，指的是节点k_{i}和FFN第二层的Value层每个节点形成连接的权重向量。每个神经元的Key向量，用于识别输入中的某种语言或者知识模式，是一种模式探测器。如果输入中包含它要检测的某种模式，那么输入向量和k_{i}节点的key权重进行向量内积计算，加上Relu，形成k_{i}的大数值响应，意味着k_{i}检测到了这个模式，于是再把这个响应值，通过k_{i}节点的Value权重向量向FFN第二层传播。这等价于将Value向量的值，用响应值加权，然后传递并体现到第二层Value层每个节点的输出上。如此这般，FFN的正向传播计算过程，看起来就像是通过Key检测到某种知识模式，然后取出对应的Value，并把Value体现在FFN的第二层输出上。当然，FFN第二层每个节点，会收集FFN的Key层所有节点信息，所以是一种混合响应，而Value层所有节点的混合响应，可以解读为代表输出单词的概率分布信息。&lt;/p&gt;      &lt;p&gt;听着可能还是比较复杂，我们用个极端的例子来说明。我们假设上图的节点k_{i}就是记载&amp;lt;北京，is-capital-of，中国&amp;gt;这条知识的Key-Value存储器，它的Key向量，用于检测”中国的首都是…”这个知识模式，它的Value向量，基本存储了与单词“北京”的Embedding比较接近的向量。当Transformer的输入是“中国的首都是[Mask]”的时候，k_{i}节点从输入层探测到这个知识模式，所以产生较大的响应输出。我们假设Key层其它神经元对这个输入都没有任何响应，那么对应的Value层的节点，其实只会接收到“北京”这个Value对应的单词embedding，并通过k_{i}的大响应值，进行了进一步的数值放大。于是，Mask位置对应的输出，就自然会输出“北京”这个单词。基本就是这么个过程，看着很复杂，其实很简单。&lt;/p&gt;      &lt;p&gt;而且这篇文章还指出，Transformer低层对句子的表层模式作出反应，高层对语义模式作出反应，就是说低层FFN存储词法、句法等表层知识，中层和高层存储语义及事实概念知识，这和其它研究结论是一致的。&lt;/p&gt;      &lt;p&gt;要我猜，把FFN看成Key-Value存储器这种思路，很可能不是最终的正确答案，但是距离最终正确答案的距离，估计也不太远。&lt;/p&gt;      &lt;h3&gt;知识涂改液：如何修正LLM里存储的知识&lt;/h3&gt;      &lt;p&gt;既然我们已知具体的某条世界知识存储在某个或者某些FFN节点的参数里，自然会引发另外一个问题：我们能否修正LLM模型里存储的错误或者过时的知识呢？比如对于问题：“英国的现任首相是谁？”鉴于近年来英国首相频繁更迭，你猜LLM更倾向输出“鲍里斯”还是更青睐“苏纳克”？很明显训练数据中包含“鲍里斯”的数据会更多，这种情况很大可能LLM会给出错误回答，于是我们就有修正LLM里存储的过时知识的必要性。&lt;/p&gt;      &lt;p&gt;如果归纳下，目前有三类不同方法来修正LLM里蕴含的知识：&lt;/p&gt;      &lt;p&gt;第一类方法从训练数据的源头来修正知识。“Towards Tracing Factual Knowledge in Language Models Back to the Training Data”这篇文章的研究目标是：对于指定的某条知识，我们是否可以定位到是哪些训练数据导致LLM学会了这条知识？答案是肯定的，这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术，假设我们想要删除某条知识，则可首先定位到其对应的数据源头，删除数据源，然后重新预训练整个LLM模型，这样即可达成删除LLM中相关知识的目的。但是这里有个问题，如果修正一小部分知识，我们就需要重新做一次模型预训练，这样做明显成本太高。所以这种方法不会太有发展前景，可能比较适合那种对于某个特定类别数据的一次性大规模删除场合，不适合少量多次的常规知识修正场景，比如可能比较适合用来做去除偏见等去toxic内容的处理。&lt;/p&gt;      &lt;p&gt;第二类方法是对LLM模型做一次fine-tuning来修正知识。一个直观能想到的方法是：我们可以根据要修正成的新知识来构建训练数据，然后让LLM模型在这个训练数据上做fine-tuning，这样指导LLM记住新的知识，遗忘旧的知识。这个方法简单直观，但是也有一些问题，首先它会带来灾难遗忘问题，就是说除了忘掉该忘的知识，还忘掉了不该忘的知识，导致这么做了之后有些下游任务效果下降。另外，因为目前的LLM模型规模非常大，即使是做fine-tuning，如果次数频繁，其实成本也相当高。对这种方法感兴趣的可以参考“Modifying Memories in Transformer Models”。&lt;/p&gt;      &lt;p&gt;另外一类方法直接修改LLM里某些知识对应的模型参数来修正知识。假设我们想要把旧知识&amp;lt;英国，现任首相，鲍里斯&amp;gt;，修正到&amp;lt;英国，现任首相，苏纳克&amp;gt;。首先我们想办法在LLM模型参数中，定位到存储旧知识的FFN节点，然后可以强行调整更改FFN中对应的模型参数，将旧知识替换成新的知识。可以看出，这种方法涉及到两项关键技术：首先是如何在LLM参数空间中定位某条知识的具体存储位置；其次是如何修正模型参数，来实现旧知识到新知识的修正。关于这类技术的细节，可以参考“Locating and Editing Factual Associations in GPT”和“Mass-Editing Memory in a Transformer”。理解这个修正LLM知识的过程，其实对于更深入理解LLM的内部运作机制是很有帮助的。&lt;/p&gt;      &lt;h2&gt;规模效应：当LLM越来越大时会发生什么&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;我们知道，近年来，LLM模型规模在快速增长，目前效果最好的LLM模型，其参数规模大都超过了千亿（100B）参数规模。比如，OpenAI的GPT 3的规模为175B，Google的LaMDA规模为137B，PaLM的规模为540B，DeepMind的Gogher规模为280B等，不一而足。国内也有中文巨型模型，比如智源GLM规模130B，华为“盘古”规模200B，百度“文心”规模260B，浪潮“源1.0”规模245B。那么，一个很自然的问题就是：随着LLM模型规模不断增长，会发生些什么呢？&lt;/p&gt;      &lt;p&gt;预训练模型的应用往往是两阶段的：预训练阶段，及具体场景应用阶段。在预训练阶段，其优化目标是交叉熵，对GPT这种自回归语言模型来说，也就是看LLM是否正确预测到了下一个单词；而场景应用阶段，一般要看具体场景的评价指标。一般我们的直觉是：如果LLM模型在预训练阶段的指标越好，自然它解决下游任务的能力就越强。然而，事实并非完全如此。现有研究已证明，预训练阶段的优化指标确实和下游任务表现出正相关关系，但是并非完全正相关。也就是说，只看预训练阶段的指标，来判断一个LLM模型是否够好，这是不够的。基于此，我们分头来看在这两个不同阶段，随着LLM模型增大，有什么影响。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2548' height='1086'&gt;&lt;/svg&gt;" width="2548"&gt;&lt;/img&gt;      &lt;p&gt;首先，我们先看在预训练阶段，随着模型规模逐步增大，会发生什么。OpenAI在“Scaling Laws for Neural Language Models”中专门研究了这个问题，并提出LLM模型所遵循的“伸缩法则”（scaling law）。如上图所示，这个研究证明：当我们独立增加训练数据量、模型参数规模或者延长模型训练时间（比如从1个Epoch到2个Epoch），预训练模型在测试集上的Loss都会单调降低，也就是说模型效果越来越好。&lt;/p&gt;      &lt;p&gt;既然三个因素都重要，那么我们在实际做预训练的时候，就有一个算力如何分配的决策问题：假设用于训练LLM的算力总预算（比如多少GPU小时或者GPU天）给定，那么是应该多增加数据量、减少模型参数呢？还是说数据量和模型规模同时增加，减少训练步数呢？此消彼长，某个要素规模增长，就要降低其它因素的规模，以维持总算力不变，所以这里有各种可能的算力分配方案。最终OpenAI选择了同时增加训练数据量和模型参数，但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了：对于训练数据量和模型参数这两个要素，如果只单独增加其中某一个，这不是最好的选择，最好能按照一定比例同时增加两者，它的结论是优先增加模型参数，然后才是训练数据量。假设用于训练LLM的算力总预算增加了10倍，那么应该增加5.5倍的模型参数量，1.8倍的训练数据量，此时模型效果最佳。&lt;/p&gt;      &lt;p&gt;DeepMind的一项研究（参考：Training Compute-Optimal Large Language Models）更深入地探究了这个问题，其基本结论和OpenAI的结论差不多，比如确实需要同时增加训练数据量和模型参数，模型效果才会更好。而很多大模型在做预训练的时候，并没有考虑这一点，很多LLM大模型只是单调增加模型参数，而固定住了训练数据量，这个做法其实是不对的，限制了LLM模型的潜力。但是它修正了两者的比例关系，认为训练数据量和模型参数是同等重要的，也就是说，假设用于训练LLM的算力总预算增加了10倍，那么应该增加3.3倍的模型参数量，3.3倍的训练数据量，这样模型效果才最好。&lt;/p&gt;      &lt;p&gt;这意味着：增加训练数据量的重要性，比我们之前所认为的，还要重要。基于这个认知，DeepMind在设计Chinchilla模型时，在算力分配上选择了另外一种配置：对标数据量300B、模型参数量280B的Gopher模型，Chinchilla选择增加4倍的训练数据，但是将模型参数降低为Gopher的四分之一，大约为70B。但是无论预训练指标，还是很多下游任务指标，Chinchilla效果都要优于规模更大的Gopher。&lt;/p&gt;      &lt;p&gt;这带给我们如下启示：我们可以选择放大训练数据，并同比例地减少LLM模型参数，以达到在不降低模型效果的前提下，极大缩小模型规模的目的。缩小模型规模有很多好处，比如在应用的时候，推理速度会快很多等，无疑这是一个很有前途的LLM发展路线。&lt;/p&gt;      &lt;p&gt;以上是从预训练阶段来看模型规模的影响，如果从LLM解决下游具体任务效果的角度来看，随着模型规模增大，不同类型的任务有不同的表现，具体而言，有以下三类情况。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2540' height='1352'&gt;&lt;/svg&gt;" width="2540"&gt;&lt;/img&gt;      &lt;p&gt;第一类任务完美体现了LLM模型的scaling law，就是说随着模型规模逐步放大，任务的表现越来越好，如上图里的（a）图所示。这类任务通常符合如下共性：它们往往都是知识密集型任务，也就是说如果LLM模型包含的知识量越多，这类任务表现越好。而很多研究已经证明越大的LLM模型学习效率越高，也就是说相同训练数据量，模型越大任务效果越好，说明面对的即使是同样的一批训练数据，更大的LLM模型相对规模小一些的模型，从中学到了更多的知识。更何况一般情况下，在增大LLM模型参数的时候，往往会同步增加训练数据量，这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图，为何随着模型规模增大，这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务，其实都属于这种知识密集型任务，而很多任务在近两年获得了极大的效果提升，甚至超过了人类表现。很明显，这大概率是LLM模型的规模增长带来的，而非归功于某项具体的技术改进。&lt;/p&gt;      &lt;p&gt;第二类任务展现出LLM具备某种“涌现能力（Emergent Ability）”，如上图（b）所示。所谓“涌现能力”，指的是当模型参数规模未能达到某个阀值时，模型基本不具备解决此类任务的任何能力，体现为其性能和随机选择答案效果相当，但是当模型规模跨过阀值，LLM模型对此类任务的效果就出现突然的性能增长。也就是说，模型规模是解锁(unlock)LLM新能力的关键，随着模型规模越来越大，会逐渐解锁LLM越来越多的新能力。这是个很神奇的现象，因为它意味着如下让人对未来可报乐观预期的可能：或许很多任务，目前LLM还不能很好地解决，甚至站在现在这个时刻的我们看起来，LLM完全没有能力解决这类任务，但因LLM具备“涌现能力”，所以如果我们继续推大模型，也许某一天它的这项能力就被突然解锁了。LLM模型的规模增长会给我们带来意想不到的精彩礼物。&lt;/p&gt;      &lt;p&gt;“Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models”这篇文章指出，这类体现出“涌现能力”的任务也有一些共性：这些任务一般由多步骤构成，要解决这些任务，往往需要先解决多个中间步骤，而逻辑推理能力在最终解决这类任务中发挥重要作用。思维链（Chain of Thought）Prompting是典型的增强LLM推理能力的技术，能大幅提升此类任务的效果，关于CoT技术，在随后小节内容会做解释，此处暂不展开。&lt;/p&gt;      &lt;p&gt;问题是，为何LLM会出现这种“涌现能力”现象呢？上述文章以及“Emergent Abilities of Large Language Models”给出了几个可能的解释：&lt;/p&gt;      &lt;p&gt;一种可能解释是有些任务的评价指标不够平滑。比如说有些生成任务的判断标准，它要求模型输出的字符串，要和标准答案完全匹配才算对，否则就是0分。所以，即使随着模型增大，其效果在逐步变好，体现为输出了更多的正确字符片段，但是因为没有完全对，只要有任何小错误都给0分，只有当模型足够大，输出片段全部正确才能得分。也就是说，因为指标不够平滑，所以不能体现LLM其实正在逐步改善任务效果这一现实，看起来就是“涌现能力”这种外在表现。&lt;/p&gt;      &lt;p&gt;另外一种可能的解释是：有些任务由若干中间步骤构成，随着模型规模增大，解决每个步骤的能力也在逐步增强，但是只要有一个中间步骤是错的，最终答案就是错的，于是也会导致这种表面的“涌现能力”现象。&lt;/p&gt;      &lt;p&gt;当然，上面的解释目前还都是猜想，至于为何LLM会出现这种现象，还需要进一步更深入的研究。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2374' height='1146'&gt;&lt;/svg&gt;" width="2374"&gt;&lt;/img&gt;      &lt;p&gt;还有少部分任务，随着模型规模增长，任务的效果曲线展现出U形特性：随着模型规模逐渐变大，任务效果逐渐变差，但是当模型规模进一步增长，则效果开始越来越好，呈现出U形增长趋势，如上图所示的粉红色PaLM模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢？“Inverse scaling can become U-shaped”这篇文章给出了一种解释：这些任务，内部其实隐含了两种不同类型的子任务，一种是真正的任务，另外一种是“干扰任务（distractor task）”。当模型规模小的时候，无法识别任意一种子任务，所以模型的表现跟随机选择答案差不多，当模型增长到中等规模的时候，主要执行的是干扰任务，所以对真正的任务效果有负面影响，体现为真正任务效果的下降，而当进一步增加模型规模，则LLM可以忽略干扰任务，执行真正的任务，体现为效果开始增长。&lt;/p&gt;      &lt;p&gt;对于那些随着模型规模增大，效果一直下降的任务，如果采用思维链（CoT）Prompting，则部分任务的表现转换为遵循Scaling law，即模型规模越大效果越好，而其它任务则转换为U性增长曲线。这其实侧面说明了：此类任务应属于推理类型的任务，所以加入CoT后任务表现会发生质的变化。&lt;/p&gt;      &lt;h2&gt;人机接口:从In Context Learning到Instruct理解&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;一般我们经常提到的人和LLM的接口技术包括：zero shot prompting、few shot prompting、In Context Learning，以及Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献，会发现叫法比较乱。&lt;/p&gt;      &lt;p&gt;其中Instruct 是ChatGPT的接口方式，就是说人以自然语言给出任务的描述，比如“把这个句子从中文翻译成英文”，类似这种。zero shot prompting我理解其实就是现在的Instruct的早期叫法，以前大家习惯叫zero shot，现在很多改成叫Instruct。尽管是一个内涵，但是具体做法是两种做法。早期大家做zero shot prompting，实际上就是不知道怎么表达一个任务才好，于是就换不同的单词或者句子，反复在尝试好的任务表达方式，这种做法目前已经被证明是在拟合训练数据的分布，其实没啥意思。目前Instruct的做法则是给定命令表述语句，试图让LLM理解它。所以尽管表面都是任务的表述，但是思路是不同的。&lt;/p&gt;      &lt;p&gt;而In Context Learning和few shot prompting意思类似，就是给LLM几个示例作为范本，然后让LLM解决新问题。我个人认为In Context Learning也可以理解为某项任务的描述，只是Instruct是一种抽象的描述方式，In Context Learning是一种例子示范的例子说明法。当然，鉴于目前这几个叫法用的有点乱，所以上述理解仅代表个人看法。&lt;/p&gt;      &lt;p&gt;所以我们此处只对In Context Learning和Instruct进行介绍，不再提zero shot和few shot了。&lt;/p&gt;      &lt;h3&gt;神秘的In Context Learning&lt;/h3&gt;      &lt;p&gt;如果你细想，会发现In Context Learning是个很神奇的技术。它神奇在哪里呢？神奇在你提供给LLM几个样本示例&amp;lt;x_{1},y_{1}&amp;gt;,&amp;lt;x_{2},y_{2}&amp;gt;....&amp;lt;x_{n},y_{n}&amp;gt;，然后给它x_{n+1}，LLM竟然能够成功预测对应的y_{n+1}。听到这你会反问：这有什么神奇的呢？Fine-tuning不就是这样工作的吗？你要这么问的话，说明你对这个问题想得还不够深入。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2672' height='1490'&gt;&lt;/svg&gt;" width="2672"&gt;&lt;/img&gt;      &lt;p&gt;Fine-tuning和In Context Learning表面看似都提供了一些例子给LLM，但两者有质的不同（参考上图示意）：Fine-tuning拿这些例子当作训练数据，利用反向传播去修正LLM的模型参数，而修正模型参数这个动作，确实体现了LLM从这些例子学习的过程。但是，In Context Learning只是拿出例子让LLM看了一眼，并没有根据例子，用反向传播去修正LLM模型参数的动作，就要求它去预测新例子。既然没有修正模型参数，这意味着貌似LLM并未经历一个学习过程，如果没有经历学习过程，那它为何能够做到仅看一眼，就能预测对新例子呢？这正是In Context Learning的神奇之处。这是否让你想起了一句歌词：“只是因为在人群中多看了你一眼  再也没能忘掉你容颜”，而这首歌名叫“传奇”。你说传奇不传奇？&lt;/p&gt;      &lt;p&gt;看似In Context Learning没从例子里学习知识，实际上，难道LLM通过一种奇怪的方式去学习？还是说，它确实也没学啥？关于这个问题的答案，目前仍是未解之谜。现有一些研究各有各的说法，五花八门，很难判断哪个讲述的是事实的真相，甚至有些研究结论还相互矛盾。这里提供几个目前的说法，至于谁对谁错，只能你自己把握了。当然，我认为追求这个神奇现象背后的真相，是一个好的研究课题。&lt;/p&gt;      &lt;p&gt;试图证明In Context Learning没有从例子中学习的工作是“Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?”。它发现了：在提供给LLM的样本示例&amp;lt;x_{i},y_{i}&amp;gt;中，y_{i}是否x_{i}对应的正确答案，其实并不重要，如果我们把正确答案y_{i}替换成随机的另外一个答案y_{j}，这并不影响In Context Learning的效果。这起码说明了一点：In Context Learning并没有提供给LLM那个从x映射到y的映射函数信息：y=f(x)，否则的话你乱换正确标签，肯定会扰乱这个y=f(x)映射函数。也就是说，In Context Learning并未学习这个输入空间到输出空间的映射过程。&lt;/p&gt;      &lt;p&gt;真正对In Context Learning影响比较大的是：x和y的分布，也就是输入文本x的分布和候选答案y有哪些，如果你改变这两个分布，比如把y替换成候选答案之外的内容，则In Context Learning效果急剧下降。&lt;/p&gt;      &lt;p&gt;总之，这个工作证明了In Context Learning并未学习映射函数，但是输入和输出的分布很重要，这两个不能乱改。&lt;/p&gt;      &lt;p&gt;有些工作认为LLM还是从给出的示例学习了这个映射函数y=f(x)，不过是种隐式地学习。比如“What learning algorithm is in-context learning? Investigations with linear models”认为Transformer能够隐式地从示例中学习x到y的映射过程，它的激活函数中包含了一些简单映射函数，而LLM通过示例能够激发对应的那一个。而“Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers”这篇文章则将ICL看作是一种隐式的Fine-tuning。&lt;/p&gt;      &lt;p&gt;总而言之，目前这还是一个未解之谜。&lt;/p&gt;      &lt;h3&gt;神奇的Instruct理解&lt;/h3&gt;      &lt;p&gt;我们可以把Instruct当作一种方便人类理解的任务表述，在这个前提下，目前关于Instruct的研究可以分成两种：偏学术研究的Instruct，以及关于人类真实需求描述的Instruct。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2676' height='1302'&gt;&lt;/svg&gt;" width="2676"&gt;&lt;/img&gt;      &lt;p&gt;我们先来看第一种：偏学术研究的Instruct。它的核心研究主题是多任务场景下，LLM模型对Instruct理解的泛化能力。如上图中FLAN模型所示，就是说有很多NLP任务，对于每个任务，研究人员构造一个或者多个Prompt模版作为任务的Instruct，然后用训练例子对LLM模型进行微调，让LLM以同时学习多个任务。训练好模型后，给LLM模型一个它没见过的全新任务的Instruct，然后让LLM 解决zero shot任务，从任务解决得是否足够好，来判断LLM模型是否有对Instruct理解的泛化能力。&lt;/p&gt;      &lt;p&gt;如果归纳下目前的研究结论（可参考“Scaling Instruction-Fine-tuned Language Models”／“Super-NaturalInstructions: Generalization via Declarative Instructions on 1600+ NLP Tasks”），能够有效增加LLM模型Instruct泛化能力的因素包括：增加多任务的任务数量、增加LLM模型大小、提供CoT Prompting， 以及增加任务的多样性。如果采取任意一项措施，都可以增加LLM模型的Instruct理解能力。&lt;/p&gt;      &lt;p&gt;第二种是人类真实需求下的Instruct，这类研究以InstructGPT和ChatGPT为代表。这类工作也是基于多任务的，但是和偏向学术研究类工作最大的不同，在于它是面向人类用户真实需求的。为什么这么说呢？因为它们用于LLM多任务训练的任务描述Prompt，是从大量用户提交的真实请求中抽样而来的，而不是固定好研究任务的范围，然后让研究人员来写任务描述prompt。这里所谓的“真实需求”，体现在两个方面：首先，因为是从用户提交的任务描述里随机抽取的，所以涵盖的任务类型更多样化，也更符合用户的真实需求；其次，某个任务的prompt描述，是用户提交的，体现了一般用户在表达任务需求时会怎么说，而不是你认为用户会怎么说。很明显，这类工作改出来的LLM模型，用户体验会更好。&lt;/p&gt;      &lt;p&gt;InstructGPT论文里，也拿这种方法和FLAN那种Instruct based方法做了比较。首先在GPT3上用FLAN提到的任务、数据以及Prompt模版进行微调，来在GPT 3上复现FLAN方法，然后和InstructGPT进行比较，因为InstructGPT的基础模型也是GPT3，所以只有数据和方法的差别，两者可比，结果发现FLAN方法的效果，距离InstructGPT有很大的差距。那么背后的原因是什么呢？论文分析数据后认为，FLAN方法涉及到的任务领域相对少，是InstructGPT涉及领域的子集，所以效果不好。也就是说，FLAN论文里涉及到的任务和用户真实需求是不符的，而这导致在真实场景下效果不够好。而这对我们的启示是：从用户数据中收集真实需求，这事情是很重要的。&lt;/p&gt;      &lt;h3&gt;In Context Learning和Instruct的联系&lt;/h3&gt;      &lt;p&gt;如果我们假设In Context Learning是用一些例子来具象地表达任务命令，Instruct是一种更符合人类习惯的抽象任务描述。那么，一个很自然的问题是：它们之间有什么联系吗？比如，我们是否能够提供给LLM完成某个任务的若干具体示例，让LLM找出其对应的自然语言描述的Instruct命令？&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2718' height='1188'&gt;&lt;/svg&gt;" width="2718"&gt;&lt;/img&gt;      &lt;p&gt;目前有零星的工作在探索这个问题，我认为这个方向是很有研究价值的。先说答案，答案是：Yes，LLM Can。“Large Language Models Are Human-Level Prompt Engineers”是做这个方向很有趣的工作，如上图所示，对于某项任务，给LLM一些示例，让LLM自动生成能够描述这项任务的自然语言命令，然后它再用LLM生成的任务描述去测试任务效果。它使用的基础模型是GPT 3和InstructGPT，经过这项技术加持后，LLM生成的Instruct的效果相比未采用这项技术的GPT 3 以及InstuctGPT来说，指标有极大地提升，而且在一些任务上超过人类的表现。&lt;/p&gt;      &lt;p&gt;这说明了：具象的任务示例和任务的自然语言描述之间，有种神秘的内在联系。至于这种联系到底是什么？我们目前对此还一无所知。&lt;/p&gt;      &lt;h2&gt;智慧之光：如何增强LLM的推理能力&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;目前很多研究已证明LLM对于知识具有强大的记忆能力，但是，一般我们不会因为一个人记忆能力强，就说这人很聪明，是否具有强大的推理能力，往往是我们判断一个人是否聪明的重要标准。类似的，如果LLM的效果想让人觉得很惊艳，强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点，去推导出新知识或新结论。关于LLM的推理能力，是最近一年来LLM里最重要和热门的研究领域之一。于是，我们关心的问题就是：LLM具备推理能力吗？如果具备，那么它的推理能力够强吗？&lt;/p&gt;      &lt;p&gt;这两个问题目前的答案似乎应该是：当模型规模足够大的时候，LLM本身是具备推理能力的，在简单推理问题上，LLM已经达到了很好的能力，但是复杂推理问题上，还需要更多深入的研究。&lt;/p&gt;      &lt;p&gt;如果梳理现有LLM推理相关工作的话，我把它们归到两大类，体现出挖掘或促进LLM推理能力不同的技术思路：第一类研究比较多，可以统称为基于Prompt的方法，核心思想是通过合适的提示语或提示样本，更好地激发出LLM本身就具备的推理能力，Google在这个方向做了大量很有成效的工作。第二类做法是在预训练过程中引入程序代码，和文本一起参与预训练，以此进一步增强LLM的推理能力，这应该是OpenAI实践出的思路。比如ChatGPT肯定具备很强的推理能力，但它并不要求用户必须提供一些推理示例，所以ChatGPT强大的推理能力，大概率来源于使用代码参与GPT 3.5的预训练。&lt;/p&gt;      &lt;p&gt;这两种思路其实大方向是迥异的：利用代码增强LLM推理能力，这体现出一种通过增加多样性的训练数据，来直接增强LLM推理能力的思路；而基于Prompt的方法，它并不会促进LLM本身的推理能力，只是让LLM在解决问题过程中更好地展示出这种能力的技术方法。可以看出，前者（代码方法）治本，后者治标。当然，两者其实也是互补的，但从长远看，治本的方法更重要。&lt;/p&gt;      &lt;h3&gt;基于Prompt的方法&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='2786' height='1412'&gt;&lt;/svg&gt;" width="2786"&gt;&lt;/img&gt;      &lt;p&gt;第一种思路是直接在问题上追加辅助推理Prompt。这种方法简单直接，但在众多领域都很有效。这个做法是由“Large language models are zero-shot reasoners”提出的，也被称为zero-shot CoT。具体而言，分为两个阶段（如上图所示），第一阶段在提问的问题上追加“Let’s think step by step”这句提示语，LLM会输出具体的推理过程；第二阶段，在第一阶段的问题后，拼接LLM输出的具体推理过程，并再追加Prompt=“Therefore, the answer (arabic numerals) is”，此时LLM会给出答案。如此简单的操作，却可以大幅增加LLM在各项推理任务中的效果，比如在数学推理测试集GSM8K上，加上提示语后，推理准确率直接从原先的10.4%提升到了40.4%，可谓神奇。&lt;/p&gt;      &lt;p&gt;为什么LLM会具备给一句“Let’s think step by step”提示语，就能列出详细的推理步骤并算出答案呢？其原因目前尚无定论，我的猜测是：很可能因为预训练数据里面存在大量的此种数据，就是以“Let’s think step by step”开头，然后后面是详细的推理步骤，最后给出答案，而LLM在预训练的时候记住了这些模式。而当我们输入这个提示语的时候，激发LLM模糊得“回忆”起某些例子的推导步骤，于是即可模仿这些例子进行步骤推理并给出答案。当然这只是我的无依据推论，若事实真的如此，如果你看过后面介绍的标准CoT做法，会发现Zero-shot CoT 本质上和标准CoT很可能没什么区别，只是标准CoT由人工来写推理步骤的示例，而Zero-shot CoT大概率是通过提示语，激活了记忆中的某些包含推理步骤的示例，很可能是如此区别。而标准CoT效果比Zero-Shot CoT效果好也完全可以理解，因为毕竟靠LLM回忆示例，精准性估计不会太高，而人工给出的示例，准确性是有保障的，所以自然标准CoT效果会更好。&lt;/p&gt;      &lt;p&gt;这侧面说明了一个道理，就是LLM本身是具备推理能力的，只是我们没有办法把它的这种能力激发出来而已，通过合适的提示语来进行两步提示，就在一定程度上可以释放出它的这种潜力。另外，对于中文，很可能存在另外一个黄金提示语，比如“详细解题思路如下”，类似这种，因为中文语料在讲解推理步骤的时候，经常用的引导句和“让我们一步一步来思考”应该是不同的，这是明显的西方说法，而探索出这个中文黄金提示语，其实也是很有必要的。&lt;/p&gt;      &lt;p&gt;第二种思路一般被称为基于示例的思维链（few-shot CoT,Chain of Thought）Prompting。这个方向目前是LLM推理研究的主方向，很多工作都是在这个思路上做的，我们简单介绍几个效果显著的代表性工作，基本能代表CoT的技术发展方向。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2194' height='1256'&gt;&lt;/svg&gt;" width="2194"&gt;&lt;/img&gt;      &lt;p&gt;CoT的主体思想其实很直白；为了教会LLM模型学会推理，给出一些人工写好的推理示例，示例里把得到最终答案前，一步步的具体推理步骤说清楚，而这些人工写的详细推理过程，就是思维链Prompting，具体例子可参照上图中蓝色文字部分。CoT的意思是让LLM模型明白一个道理；就是在推理过程中，步子不要迈得太大，否则很容易出错，改变思维模式，化大问题为小问题，步步为营，积小胜为大胜。最早明确提出CoT这个概念的文章是“Chain of thought prompting elicits reasoning in large language models”，论文发布于22年1月份，虽然做法很简单，但是应用CoT后LLM模型的推理能力得到了巨大提升，GSM8K数学推理测试集准确率提高到60.1%左右。当然，这种给出详细推理步骤和中间过程的思想，并非CoT最早提出的，更早一些的“scratchpad”技术（可参考：Show Your Work: Scratchpads for Intermediate Computation with Language Models）首先采用了类似的思路。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2418' height='1296'&gt;&lt;/svg&gt;" width="2418"&gt;&lt;/img&gt;      &lt;p&gt;CoT提出不久，很快在22年3月份，一项被称为“Self-Consistency”的改进技术就将GSM8K测试集准确率提高到74.4%，提出这项改进的论文是“Self-Consistency Improves Chain of Thought Reasoning in Language Models”。“Self-Consistency”的思路也很直观（参考上图）：首先可以利用CoT给出几个写了推理过程的示例，然后要求LLM对给定的问题进行推理，如果是CoT，直接输出一个推理过程和答案，整个过程就结束了。“Self-Consistency”则不然，它要求LLM输出多个不同的推理过程和答案，然后采用投票的方式选出最佳答案，思路非常简单直接，但是效果也确实好。“Self-Consistency”其实是教导LLM学会这么一个道理：孔乙己说过茴香豆的“茴”字有四种写法，类似的，一个数学题的正确解法也可以有很多种，每个不同的推导过程都指向最终的答案。条条大路通罗马，虽说也有个别迷路走到北京的，但是迷路的毕竟是少数，看看大多数人走到哪里，哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义，是不是这道理？&lt;/p&gt;      &lt;p&gt;再往后，“On the Advance of Making Language Models Better Reasoners”这个工作在“Self-Consistency”基础上，进一步集成了“从一个Prompt问题拓展到多个Prompt问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票”这三个改进点，将GSM8K测试集准确率提高到83%左右。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2056' height='1492'&gt;&lt;/svg&gt;" width="2056"&gt;&lt;/img&gt;      &lt;p&gt;第三种思路体现了一种分治算法的思想。当然这个所谓“分治”是我归纳的，别人没这么说。这种思路的核心思想是：对于一个复杂的推理问题，我们把它分解成若干容易解决的子问题，一一解决掉子问题后，我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得，这种思路可能才是揭示问题本质、最终解决LLM复杂推理问题正宗的道路。我们以“Least-to-most prompting”技术为例来说明这种思路的一种具体实现方式，如上图所示：它分为两个阶段，第一个阶段，从原始问题我们可以得知最终要问的问题是什么，我们假设最终问题是Final Q，然后从原始问题填充Prompt模版：“如果要解决Final Q问题，那么我需要先解决”，然后把原始问题和这个Prompt交给LLM，让LLM模型给出答案，等于让LLM给出最终问题的前置子问题Sub Q；接下来我们进入第二个阶段，让LLM先回答刚才拿到的子问题Sub Q，并拿到对应的答案，然后原始问题拼接子问题Sub Q及对应答案，再去问LLM最终那个问题Final Q，此时LLM会给出最后的答案。如此这般，体现出拆解子问题，并从子问题的答案逐步找出最终答案的思路。&lt;/p&gt;      &lt;h3&gt;代码预训练增强LLM推理能力&lt;/h3&gt;      &lt;p&gt;以上是目前利用Prompt激发LLM模型推理能力的三种主流做法，而关于LLM的推理能力，目前还观察到一个有趣且费解的现象：除了文本外，如果能够加入程序代码一起参与模型预训练，则能大幅提升LLM模型的推理能力。这个结论从不少论文的实验部分都可以得出（可以参考：AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS／Challenging BIG-Bench tasks and whether chain-of-thought can solve them等论文的实验部分）。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2770' height='1164'&gt;&lt;/svg&gt;" width="2770"&gt;&lt;/img&gt;      &lt;p&gt;上图给出了一份实验数据，来自于论文“On the Advance of Making Language Models Better Reasoners”，其中GPT3 davinci就是标准的GPT 3模型，基于纯文本训练；code-davinci-002（OpenAI内部称为Codex）是同时在Code和NLP数据上训练的模型。如果比较两者效果，可以看出，不论采用具体哪种推理方法，仅仅是从纯文本预训练模型切换到文本和Code混合预训练模型，在几乎所有测试数据集合上，模型推理能力都得到了巨大的效果提升，比如我们以“Self Consistency”方法为例，在大多数据集合上的性能提升，都直接超过了20到50个百分点，这是很恐怖的性能提升，而其实在具体推理模型层面，我们什么也没做，仅仅是预训练的时候除了文本，额外加入了程序代码而已。&lt;/p&gt;      &lt;p&gt;除了这个现象，从上图数据中，我们还可以得出其它一些结论，比如GPT 3这种纯文本预训练模型，其实是具备相当程度的推理能力的，除了在GSM8K这种数学推理上效果比较差外，其它推理数据数据集合表现也还可以，前提你需要采用合适的方法，来激发出它本身就具备的这种能力；再比如，text-davinci-002，也就是在code-davinci-002基础上加入instruct fine-tuning后的模型（就是加入InstructGPT或ChatGPT模型的第一步），其推理能力要弱于Codex，但是有其它研究表明它在自然语言处理任务又要强于Codex。而这貌似说明了，加入instruct fine-tuning，会损害LLM模型的推理能力，但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的，也能启发后续进一步的思考和探索。&lt;/p&gt;      &lt;p&gt;那么，一个自然的疑问是：为何预训练模型可以从代码的预训练中获得额外的推理能力？确切原因目前未知，值得深入探索。我猜测可能是因为原始版本的Codex（只使用代码训练，可参考文献：Evaluating Large Language Models Trained on Code）的代码训练是从文本生成代码，而且代码中往往包含很多文本注释，本质上这类似于预训练模型做了&amp;lt;文本,Code&amp;gt;两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释，很明显这些数学类或逻辑推理类的数据，对于解决下游数学推理问题是有帮助的，我猜大概率原因在此。&lt;/p&gt;      &lt;h3&gt;关于LLM推理能力的思考&lt;/h3&gt;      &lt;p&gt;上面介绍了LLM推理的主流技术思路和现有的一些结论，接下来谈谈我对LLM模型推理技术的思考，以下内容纯个人推断，没有太多证据，还请谨慎参考。我的判断是：虽然最近一年来，关于激发LLM的推理能力，这方面的技术进展很快，也取得了很大的技术进步，但是总体感觉是，我们可能走在正确的方向上，但是距离接触到真正的问题本质还有一段距离，对此要有更深入的思考和探索。&lt;/p&gt;      &lt;p&gt;首先，我比较赞同上述分治算法的主体思路，对于复杂的推理问题，我们应该把它拆解成若干简单的子问题，因为子问题对于LLM来说回答正确的概率就大很多，让LLM一一回答子问题后，再逐步推导出最终答案。受到“Least-to-most prompting”技术的启发，如果进一步思考，我觉得LLM推理本质上很可能会是如下两种可能的其中之一：不断和LLM进行交互的图上推理问题，抑或是不断和LLM进行交互的程序流程图执行问题。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2654' height='1050'&gt;&lt;/svg&gt;" width="2654"&gt;&lt;/img&gt;      &lt;p&gt;先说图上推理问题，如上图所示，假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构，图中的节点是子问题或者子步骤，图中的边代表了子问题之间的依赖关系，就是说只有回答好子问题A，才能回答子问题B，而且图中大概率存在循环结构，就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图，那么可以根据依赖关系，引导LLM一步一步按照图结构，回答必须首先回答的子问题，直到推导出最终答案。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2662' height='1112'&gt;&lt;/svg&gt;" width="2662"&gt;&lt;/img&gt;      &lt;p&gt;再说程序流程图问题，参考上图，假设我们有办法把复杂问题拆解成子问题或子步骤，并产生一个由子步骤构成的类似程序流程图的结构，在这个结构里，有些步骤会反复执行多次（循环结构），有些步骤的执行需要进行条件判断（条件分支）。总而言之，在执行每个子步骤的时候和LLM进行交互，得到子步骤的答案，然后按照流程不断执行，直到输出最终答案。类似这种模式。假设这个思路大致正确的话，也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力：大概率因为&amp;lt;文本，代码&amp;gt;的多模态预训练模型，在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁，将两者联系起来的，即由文本描述到隐含的流程图，再映射到由流程图产生具体的代码。也就是说，这种多模态预训练，可以增强LLM模型从文本构建出隐含的流程图并按照流程图执行的能力，也就是加强了它的推理能力。&lt;/p&gt;      &lt;p&gt;当然，上述思路最大的问题是，我们如何根据文本描述的问题，能够靠LLM模型，或者其它模型，得到图结构或者流程图结构？这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练，走隐式学习内部隐含结构的方法。而目前的CoT技术，如果套到上述思路来思考的话，可以这么理解：标准CoT，其实就是靠自然语言文本来描述图结构或者程序流程图的；而“Least-to-most prompting”技术，则是试图根据最后一个图节点，靠倒推来试图推导出其中的图结构，但是很明显，目前的方法限制了它倒推的深度，也就是说它只能推导出非常简单的图结构，这正是限制它能力的所在。&lt;/p&gt;      &lt;h2&gt;未来之路：LLM研究趋势及值得研究的重点方向&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;这里列出一些我个人认为比较重要的LLM研究领域，或值得深入探索的研究方向。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;探索LLM模型的规模天花板&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;尽管继续推大LLM模型的规模，这事看似没有技术含量，但是其实这个事情异常重要。我个人判断，自从Bert出现以来，到GPT 3，再到ChatGPT，大概率这些给人印象深刻的关键技术突破，核心贡献都来自于LLM模型规模的增长，而非某项具体技术。说不定，揭开AGI真正的钥匙就是：超大规模及足够多样性的数据、超大规模的模型，以及充分的训练过程。再者，做超大规模的LLM模型，对技术团队的工程实现能力要求是非常高的，也不能认为这事情缺乏技术含量。&lt;/p&gt;      &lt;p&gt;那么继续推大LLM模型规模，有什么研究意义呢？我觉得有两方面的价值。首先，如上所述，我们已知，对于知识密集型的任务，随着模型规模越大，各种任务的效果会越来越好；而对很多推理类型的有难度的任务，加上CoT Prompting后，其效果也呈现出遵循Scaling law的趋向。那么，很自然的一个问题就是：对于这些任务，LLM的规模效应，能将这些任务解决到何种程度？这是包括我在内，很多人关心的问题。其次，考虑到LLM具备的神奇的“涌现能力”，如果我们继续增加模型规模，它会解锁哪些让我们意想不到的新能力呢？这也是很有意思的问题。考虑到以上两点，我们仍然需要不断增大模型规模，看看模型规模对解决各类任务的天花板在哪里。&lt;/p&gt;      &lt;p&gt;当然，这种事情也就只能说说，对99.99%的从业者来说，是没有机会和能力做这个事情的。要做这个事情，对研究机构的财力及投入意愿、工程能力、技术热情，都有极高的要求，缺一不可。能做这事情的机构，粗估下来，国外不超过5家，国内不超过3家。当然，考虑到成本问题，未来也许会出现“股份制大模型”，就是有能力的几家机构合作，群策群力，一起来共建超级大模型的现象。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;增强LLM的复杂推理能力&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;正如之前对LLM推理能力的叙述，尽管LLM在最近一年推理能力得到了很大的提升，但是很多研究（参考：Limitations of Language Models in Arithmetic and Symbolic Induction／Large Language Models Still Can’t Plan）表明，目前LLM能够解决得比较好的推理问题，往往都相对简单，LLM的复杂推理能力仍然薄弱，比如即使是简单的字符拷贝推理或者加减乘除运算，当字符串或者数字非常长的时候，LLM推理能力会极速下降，再比如行为规划能力等复杂推理能力很弱。总而言之，加强LLM的复杂推理能力，应该是LLM未来研究中最重要的环节之一。&lt;/p&gt;      &lt;p&gt;前文有述，加入代码加入预训练，这是一种直接增强LLM推理能力的方向。这个方向目前研究尚显不足，更像是实践经验的总结，探索背后的原理，并进而引入更多类型除代码外的新型数据来增强LLM的推理能力，这可能是更本质提升推理能力的方向。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;LLM纳入NLP之外更多其它研究领域&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;目前的ChatGPT擅长NLP和Code任务，作为通向AGI的重要种子选手，将图像、视频、音频等图像与多模态集成进入LLM，乃至AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入LLM，是LLM通往AGI的必经之路。而这个方向才刚刚开始，因此具备很高的研究价值。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;更易用的人和LLM的交互接口&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;如前所述，ChatGPT的最大技术贡献即在此。但是很明显，目前的技术并不完美，肯定还有很多命令LLM理解不了。所以，沿着这个方向，寻找更好的技术，来让人类使用自己习惯的命令表达方式，而LLM又能听懂，这是个新的，且非常有前景的技术方向。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;建设高难度的综合任务评测数据集&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;好的评测数据集，是引导技术不断进步的基石。随着LLM模型逐步增大，任务效果快速提升，导致很多标准测试集快速过时。也就是说，这些数据集合相对现有技术来说，太容易了，在没有难度的测试集合下，我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合，是促进LLM技术进步的关键所在。&lt;/p&gt;      &lt;p&gt;目前行业应出现了一些新的测试集，有代表性的包括BIGBench、OPT-IML等。这些测试集合体现出一些特性，比如相对LLM现有技术具备一定的难度、综合了各种各样多种类型的任务等。&lt;/p&gt;      &lt;p&gt;受到ChatGPT的启发，我觉得除此外应纳入另一考虑因素：体现真实用户需求。就是说，这些任务的表述由用户真实发起，这种方式构建出来的LLM模型，才能解决用户实际需求。&lt;/p&gt;      &lt;p&gt;除此外，相信LLM会快速将能力溢出到NLP之外的领域，而如何融入更多其它领域的评测数据，也是需要提前去考虑。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;高质量数据工程&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;对于预训练模型来说，数据是其根本，预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此，我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。&lt;/p&gt;      &lt;p&gt;关于数据，需要考虑两个方面：数据的质量和数量。而根据T5的对比实验，我们可以得出结论：在数量和质量两个因素里，质量优先，正确的道路应该是在保证数据质量的前提下，再去增大数据规模。&lt;/p&gt;      &lt;p&gt;数据质量，包括数据的信息含量以及数据的多样性等多个衡量标准，比如Wiki明显就属于世界知识密度极高的高质量数据，这是从信息含量来说的；而增加数据类型的多样性，无疑是激发LLM各种新能力的根本，比如加入问答网站的数据，对于LLM的QA能力提升是有直接帮助的。多样化的数据赋予了LLM更好解决更多不同类型任务的能力，所以，这可能是数据质量里最关键的标准。&lt;/p&gt;      &lt;p&gt;关于数据数量，原则上互联网上公开发布的数据都可以纳入LLM模型的预训练过程。那么，它的极限在哪里？“Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning” 对此进行了估算，结论是到2026年左右，高质量的NLP数据将会用光，低质量NLP数据会在2030到2050年用光，而低质量图像数据会在2030到2060年用光。而这意味着：要么到时我们有新类型的数据源，要么我们必须增加LLM模型对数据的利用效率。否则，目前这种数据驱动的模型优化方式将会停止进步，或者收益减少。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;超大LLM模型Transformer的稀疏化&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;目前规模最大的LLM中，有相当比例的模型采取了稀疏（Sparse）结构，比如GPT 3、PaLM、GLaM等，GPT 4大概率也会走稀疏模型路线。之所以采用Sparse 化的模型，主要好处是它可以极大减少LLM的训练时间和在线推理时间。Switch Transformer论文里指出：在相同算力预算的前提下，使用稀疏化Transformer，相对Dense Transformer，LLM模型的训练速度可以提升4倍到7倍。为何Sparse模型可以加快训练和推理时间呢？这是因为尽管模型参数巨大，但是对于某个训练实例，Sparse模型通过路由机制，只使用整个参数中的一小部分，参与训练和推理的活跃参数量比较少，所以速度快。&lt;/p&gt;      &lt;p&gt;我认为未来超大的LLM模型大概率会收敛到稀疏模型。主要有两个原因：一方面，现有研究表明（参考：Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers），标准的Dense Transformer在训练和推理时，它本身也是稀疏激活的，就是说只有部分参数会被激活，大部分参数没有参与训练和推理过程。既然这样，我们不如直接迁移到稀疏模型；另外，毫无疑问LLM模型的规模会继续推大，而高昂的训练成本是妨碍其进一步扩大模型的重要阻力，使用稀疏模型可以极大降低超大模型的训练成本，所以随着模型规模越大，稀疏模型带来的收益越明显。考虑到这两个方面，大概率未来更大的LLM模型会采用稀疏模型方案。&lt;/p&gt;      &lt;p&gt;那为何目前其它大规模模型不走稀疏模型的路线呢？因为Sparse模型存在训练不稳定、容易过拟合等问题，不太容易训练好。所以，如何修正稀疏模型面临的问题，设计出更容易训练的稀疏模型，是很重要的未来研究方向。&lt;/p&gt;      &lt;h2&gt;取经之路：复刻ChatGPT时要注意些什么&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;如果希望能复刻类似ChatGPT这种效果令人惊艳的LLM模型，综合目前的各种研究结论，在做技术选型时需要重点权衡如下问题：&lt;/p&gt;      &lt;p&gt;首先，在预训练模式上，我们有三种选择：GPT这种自回归语言模型，Bert这种双向语言模型，以及T5这种混合模式(Encoder-Decoder架构，在Encoder采取双向语言模型，Decoder采取自回归语言模型，所以是一种混合结构，但其本质仍属于Bert模式)。我们应选择GPT这种自回归语言模型，其原因在本文范式转换部分有做分析。目前看，国内LLM在做这方面技术选型的时候，貌似很多都走了Bert双向语言模型或T5混合语言模型的技术路线，很可能方向走偏了。&lt;/p&gt;      &lt;p&gt;第二，强大的推理能力是让用户认可LLM的重要心理基础，而如果希望LLM能够具备强大的推理能力，根据目前经验，最好在做预训练的时候，要引入大量代码和文本一起进行LLM训练。至于其中的道理，在本文前面相关部分有对应分析。&lt;/p&gt;      &lt;p&gt;第三，如果希望模型参数规模不要那么巨大，但又希望效果仍然足够好，此时有两个技术选项可做配置：要么增强高质量数据收集、挖掘、清理等方面的工作，意思是我模型参数可以是ChatGPT/GPT 4的一半，但是要想达到类似的效果，那么高质量训练数据的数量就需要是ChatGPT/GPT 4模型的一倍（Chinchilla的路子）；另外一个可以有效减小模型规模的路线是采取文本检索（Retrieval based）模型+LLM的路线，这样也可以在效果相当的前提下，极大减少LLM模型的参数规模。这两个技术选型不互斥，反而是互补的，也即是说，可以同时采取这两个技术，在模型规模相对比较小的前提下，达到超级大模型类似的效果。&lt;/p&gt;      &lt;p&gt;第四，超级大模型因为模型规模大，所以训练成本过高，导致很少有机构有能力去做这件事。而且由上文分析可见，继续不断推大LLM模型规模是肯定会发生、也应该去做的事情。于是，如何通过技术手段降低LLM的训练成本就很重要。LLM的特征抽取器Sparse化是有效降低模型训练及推理成本的技术选择。由此可见，随着模型越来越大，LLM模型Sparse化是一个应该考虑的选项。&lt;/p&gt;      &lt;p&gt;第五，ChatGPT是目前最接近理想LLM的技术方案，而理想中的LLM应该是以一个几乎无所不能的基础通用大模型作为依托，来支持各种各样的上层任务类型。目前看，支持越来越多的任务类型，主要是通过增加LLM预训练数据的多样性来达成的，数据多样性越好，LLM能够支持的任务类型就越丰富。所以，应该重视通过增加数据多样性来增加LLM新能力的思路。&lt;/p&gt;      &lt;p&gt;第六，易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务，而LLM要能够理解这些Instruct的真实含义。另外，也要注意这些Instruct是符合人类真实需求的，就是说，要从最终用户那里收集任务表述方式，而不能靠研发人员自己的臆想或猜测。ChatGPT给我最大的启发其实是这一点，至于是否用增强学习我倒觉得不重要，其它替代技术应该也能做类似的事情。&lt;/p&gt;      &lt;h2&gt;ChatGPT:为什么是OpenAI&lt;/h2&gt;      &lt;hr&gt;&lt;/hr&gt;      &lt;p&gt;为什么是OpenAI作出了ChatGPT，而不是其它机构呢？我们在这里可以做个简单分析。&lt;/p&gt;      &lt;p&gt;在本文开头，我们提到了OpenAI看待LLM的理念。OpenAI是怎么看待LLM的呢？回顾它不断推出的技术，可以看出，它其实从GPT 1.0开始，基本就坚定地把LLM看做是通往AGI的一条必由之路。具体而言，在OpenAI眼中，未来的AGI应该长这个样子：有一个任务无关的超大型LLM，用来从海量数据中学习各种知识，这个LLM以生成一切的方式，来解决各种各样的实际问题，而且它应该能听懂人类的命令，以便于人类使用。其实对LLM发展理念的理解，在前半部分，就是“构建一个任务无关的超大型LLM，让它从海量数据中学习各种知识”，这一点几乎是大家的共识，能体现出OpenAI眼光的其实是后半部分。&lt;/p&gt;      &lt;p&gt;OpenAI的理念比较超前，对自我定位从一开始就定得比较高，始终坚定不移地探索上述方式是否可以实现AGI。OpenAI之所以能作出ChatGPT，胜在一个是定位比较高，另一个是不受外界干扰，态度上坚定不移。&lt;/p&gt;      &lt;p&gt;我们可以回顾下它走的一些关键路程：GPT 1.0走的是生成模式的自回归语言模型路线，比Bert出来的还早些。Bert证明了：双向语言模型对于很多NLP理解类任务，效果比自回归这种单向语言模型效果更好。尽管如此，GPT 2.0并没有因此切换到双向语言模型这条路上，仍然走文本生成的路，而且开始尝试零示例（zero shot）prompt和少量示例（few shot）prompt。其实这时候， OpenAI心目中的AGI已经开始浮出水面，逐渐显示出轮廓了。只是因为zero shot/few shot效果比Bert+fine-tuning差的比较远，所以大家都没太当回事，甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候，我估计即使是OpenAI自己，也不一定能确保这条路肯定能走通。&lt;/p&gt;      &lt;p&gt;但是，这不妨碍它继续在这条路上往后走。GPT 3.0已经展示出了比较强大的zero shot/few shot prompt能力，这时候OpenAI心目中的AGI已经完全漏出水面，轮廓清晰，而且它的效果也证明了这条路，是有较大可能走得通的。GPT 3.0是一个决定LLM发展方向的叉路口和分水岭，与之对应的另外一条路是“Bert+fine-tuning”模式。在这个岔路口，不同的从业者选择走上了不同的道路，后面的技术差距也是从这里开始拉开的。很遗憾地是，国内很多从业者选择继续在“Bert+fine-tuning”这条路上往后走，这也是造成今天落后局面的一个关键时间节点。再往后，就是InstructGPT和ChatGPT了，OpenAI通过ChatGPT证明了一点；虽然我们距离真正的AGI，可能还有很长的路要走，但是通过超大LLM走向AGI这条路，目前看是可行的。&lt;/p&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/62585-agi-%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B-llm</guid>
      <pubDate>Thu, 12 Jan 2023 16:03:22 CST</pubDate>
    </item>
    <item>
      <title>技术美术会是一个长期存在的职业吗？</title>
      <link>https://itindex.net/detail/62578-%E6%8A%80%E6%9C%AF-%E7%BE%8E%E6%9C%AF-%E5%AD%98%E5%9C%A8</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;strong&gt;技术美术&lt;/strong&gt;，英文名是 Technical Artist，简称   &lt;strong&gt;TA&lt;/strong&gt;。他们是最懂程序的美术，也是最懂美术的程序，他们隐藏在游戏研发团队里，近几年也开始成立专门的 TA 小组。在国内游戏行业，技术美术这个职业，正在逐渐崭露头角......&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='200' height='249'&gt;&lt;/svg&gt;" width="200"&gt;&lt;/img&gt;  &lt;p&gt;技术美术到底是一个什么样的概念？这份职业会长期存在吗？   &lt;strong&gt;我们邀请到了六位来自不同背景的技术美术同事，请他们谈一谈在天美作为TA的经历，以及对这份职业的展望。&lt;/strong&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— 未来的重要趋势是技术美术的中台化。&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;随着游戏玩法的不断复杂化，以及游戏画面品质的不断提升，研发团队也需要产生更加精细化的分工 —— 技术美术岗位最早就是在这样的重度品质要求下诞生的。&lt;/p&gt;  &lt;p&gt;回顾早期的手机游戏开发团队，一个团队里顶多有三到五个美术岗位，几个2D原画设计，再配几个UI设计就行了。&lt;/p&gt;  &lt;p&gt;后来有了早期的3D手机游戏，但团队构成无非就是在2D美术人员的基础上，再加几个3D场景、角色和动画岗位即可。&lt;/p&gt;  &lt;p&gt;可是随着手机硬件的提升，移动端游戏开始慢慢向端游的3A品质方向发展，越来越多重度内容被加到游戏中，引擎功能也变得极为庞大和复杂。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1640' height='1000'&gt;&lt;/svg&gt;" width="1640"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;于是3D和2D美术越来越难专注到内容制作中去，经常要耗费大量精力去解决美术资源的导入，检查和编辑器内效果提升的任务。同时，在客户端程序方面，程序员也不能集中精力去解决游戏玩法和逻辑的问题，而是要不停地兼顾大量效果特性的实现。   &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;strong&gt;效果提升和引擎对接方面&lt;/strong&gt;的工作，包括编写材质着色器，改进引擎渲染管线，提供DCC工具的资源导出和检测工具，编写引擎内用的美术自动化工具，以及进行移动端性能优化等等。&lt;/p&gt;  &lt;p&gt;当然，随着游戏行业的发展，以及游戏内资源制作量不断增加，   &lt;strong&gt;现在技术美术的工作有了新的分工和变化&lt;/strong&gt;。其中一块主要还是为了解决大型项目的研发效率和成本问题，比如引入过程化资源生成和程序贴图的使用，为此很多公司还专门新增了Houdini制作这个岗位。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;解决成本的另一个方法就是实行工业化和流水线化&lt;/strong&gt;，比如PBR渲染概念的引入，就是把贴图和材质制作设定统一标准，美术资源可以在不同的软件、引擎和项目中通用，保证最终品质的统一性。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;另外很多动画和特效的技术开发现在也需要专门的技术美术来处理&lt;/strong&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='1640' height='900'&gt;&lt;/svg&gt;" width="1640"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &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;strong&gt;第一是工作解耦&lt;/strong&gt;：项目作为业务团队，能把更多精力放在应用层面的开发，确保游戏玩法、逻辑、交互、AI等功能需求，而不用去解决画面渲染、物理特效等重度品质特性的实现。&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;第三是资源共享&lt;/strong&gt;：技术美术中台能归纳总结多个项目的研发经验，对每个品质点的提升方案，都能提炼出性价比最高的技术路线，并及时分享给项目组使用，减少了项目组试错的时间和成本。&lt;/p&gt;  &lt;p&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='775' height='430'&gt;&lt;/svg&gt;" width="775"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;   &lt;strong&gt;对于技术美术个人来说，采用中台化的组织架构，也更利于个人发展：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1.提升知识的广度和深度&lt;/strong&gt;：TA跨项目支持能获得更多经验，避免长期做单一类型项目导致技术停滞发展，同时利用中台的技术和资源，深入挖掘某个领域，提升研究深度；&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;2.获得更多专业人员的支持&lt;/strong&gt;：TA集体作战，能得到更多同行的支持，个人遇到工作问题可以更好地解决；&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.做专属TA岗位&lt;/strong&gt;：很多TA以前在项目里，因为数量少，无法成立单独的组织架构，要么属于美术来管理，要么属于程序来管理，个人定位不是很清楚。现在中台部门就能给与其充分的归属感，同时个人的职业发展路线也可以规划地更加清晰和明确。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;4.更优质的学习成长机会&lt;/strong&gt;：TA中台部门可以参与跨项目交流和各种中台组织对接，这些都是项目组TA很难获得的机会。另外中台部门还可以提供专业性更强的TA团建活动，获得跟同行宝贵的交流经验。&lt;/p&gt;  &lt;p&gt;总结来说，从项目组内的单个TA，到各种分工的TA，再到成立TA中台部门，   &lt;strong&gt;技术美术的发展趋势就像游戏行业的发展一样，从小到大，从弱到强，最终形成组织化正规化的集体架构&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;未来，各个业内公司必然都将加强中台部门的建设，提升开发效率降低研发成本。   &lt;strong&gt;如何提升中台部门的管理模式，加强团队的战斗力和凝聚力，做好品质和性能上的突破，并更好地服务于一线项目，是未来我们需要探索的重要领域。&lt;/strong&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— 目前市场上对技术美术的需求分为了美术向TA，渲染向TA。我又加了两种：流程向TA和综合型TA。&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;随着我国游戏行业的发展，国内游戏公司正在向国际化AAA大厂看齐。未来甚至在某些方面，会超过国外老牌游戏公司。&lt;/p&gt;  &lt;p&gt;技术美术是一个国内的新兴职业，它的出现，以及其大大提高的地位，都是时代的产物。&lt;/p&gt;  &lt;h2&gt;技术美术的不可替代性&lt;/h2&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;strong&gt;技术美术能大大提高游戏美术生产流程的效率。&lt;/strong&gt;当需要制作海量游戏资源的时候，团队可以通过PCG生成来制作海量资源，也可以通过一系列美术工作流工具（离线预览工具、自动生成工具、便捷操作工具等等）加快迭代速度，并且可以大幅度降低出错概率。最终每一个部分游戏资源的输出端也会进行错误自动排查（资源上传工具、资源验证工具等等），使游戏美术类bug数量降到最低。&lt;/p&gt;  &lt;p&gt;也就是说，   &lt;strong&gt;当技术美术融合在生产管线中时，团队将会节省出大量的研发资金和开发时间&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;技术美术也会在游戏制作流程方面，与美术和程序进行深度配合，使双方沟通更加顺畅。&lt;/strong&gt;因为技术美术的综合能力较强，所以可以快速定位难以定性的疑难杂症，并将问题解决，或者分发给对应的制作人员。&lt;/p&gt;  &lt;p&gt;此外，因为技术美术能够熟悉使用游戏引擎或DCC软件，所以他们能够快速帮助美术开发人员定位问题，并提供一些便捷操作的方法。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.在技术推进方面，技术美术可以对最新游戏渲染和引擎技术进行最快速落地的画面验证，并达到非常好的效果 —— 这是纯程序和纯美术的重要瓶颈。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;因为技术美术是技术和美术的充分融合，所以技术美术可以经常变通地分析和解决问题。他们可以通过技术研究，算法简化，数据拟合，甚至使用引擎或DCC软件的奇怪操作，来实现多种效果。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1450' height='720'&gt;&lt;/svg&gt;" width="1450"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;h2&gt;技术美术的分工&lt;/h2&gt;  &lt;p&gt;技术美术由不同的生产管线进化而来，他们是某一领域的内容生产专家，并对技术美术有很深的热情。他们通过不断的学习，才转为技术美术。&lt;/p&gt;  &lt;p&gt;根据各个大厂的招聘需求，技术美术的细分分工已经逐渐明确。   &lt;strong&gt;目前市场上对技术美术的需求分为了美术向TA，渲染向TA。我又加了两种：流程向TA和综合性TA。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1.美术向TA&lt;/strong&gt;：美术向TA分为许多种，大型项目中基本美术的所有工种都可以分配对应TA，包括模型、动画、特效等各个管线。有一部分美术TA也承担一些顶层渲染代码的编辑工作，来帮助美术同事快速制作出特殊要求的游戏效果。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;渲染方面，我觉得二次元方向更倾向于美术TA来跟进开发。&lt;/strong&gt;因为许多高校的专业动画系毕业生如果从事二次元渲染开发的话，对二次元渲染会有着更加深刻的认知，例如线条的流畅性、色彩分布、笔触用法、动画变形等等。此类对于赛璐璐动画的深入研究，是程序向TA所不具备的。&lt;/p&gt;  &lt;p&gt;不过美术向TA实际上也并没有迈过程序的槛，大部分人还是无法完成专业程序代码的编写，且不具备算法研究能力。但是就美术表现而言，美术向TA也是非常重要的了，配合程序向TA，可以让渲染达到极高的画面表现，在应用层取得最好的表现效果。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;2.渲染向TA&lt;/strong&gt;：大部分程序向TA研究图形渲染，还有一小部分程序向TA研究物理引擎。在一些公司里，程序向TA也包含引擎程序员。&lt;/p&gt;  &lt;p&gt;程序向TA具有强大的编程和算法研究能力，可以快速对顶尖的学术论文和最新技术进行攻坚，实现底层渲染管线的搭建，并优化运算效率。他们可以通过光学算法，使画面更接近物理渲染的真实感，表现得更好更真实。&lt;/p&gt;  &lt;p&gt;目前流行的多种多样的开发选型都要靠程序向TA来完成，如PBR渲染、光线追踪、各种抗锯齿、后处理、物理模拟等等。游戏的运行效率优化很大一部分也要靠他们来完成。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='965' height='520'&gt;&lt;/svg&gt;" width="965"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;   &lt;strong&gt;3.管线向TA&lt;/strong&gt;：实际工作中， 很多美术同学会认为程序向TA写的工具不好用，交互性差，怎么学都用不明白。这是因为纯程序向TA开发的美术工具，通常无法达到“使用者即开发者”的目标，还需要管线向TA的加工。&lt;/p&gt;  &lt;p&gt;管线向TA是一个专精的工种，所以我没有把它归入程序向TA。管线向TA要对游戏美术管线非常熟悉，甚至经常使用，配合美术资源的大量生成和制作，并精通美术DCC软件，这样团队才能生产出一系列好用的工具。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;4.综合型TA&lt;/strong&gt;：综合型TA是技术美术中最综合的选手，他们的学习任务量大，并且需要通过长期的项目沉淀，才能将各方面知识融会贯通。美术或程序想要成为综合型TA，其难度要高于成为程序向TA和美术向TA的难度。&lt;/p&gt;  &lt;p&gt;综合型TA需要精通以上全部专业，并各方面达到专业标准。综合型TA的宏观技术规划能力会比较强，对技术和美术的结合运用与技术选型也会达到一个新的高度。&lt;/p&gt;  &lt;h2&gt;技术美术的优势&lt;/h2&gt;  &lt;p&gt;1.快速跟进研发最新的游戏引擎与渲染技术；&lt;/p&gt;  &lt;p&gt;2.在性能优化的同时，提高游戏画面表现；&lt;/p&gt;  &lt;p&gt;3.使公司美术资源生产成本和时间大大降低；&lt;/p&gt;  &lt;p&gt;4.通过工具大量减少或杜绝资源出错概率；&lt;/p&gt;  &lt;p&gt;5.减少程序与美术沟通成本，疑难杂症快速解决定位。&lt;/p&gt;  &lt;p&gt;现在游戏公司里的技术美术岗位越来越细分，这说明业界对技术美术的认知正在提高，技术美术的重要性也正在上升。技术美术将成为一个常青树职位。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1600' height='900'&gt;&lt;/svg&gt;" width="1600"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;我刚开始了解到技术美术的时候，这个职业还没有细分，当时我的梦想只是成为一个合格的TA，但是现在我的奋斗目标是成为一个综合型TA。   &lt;br /&gt;   &lt;br /&gt;人的时间精力有限，只能不断沉淀学习，不可能一蹴而就，样样精通，望各位共勉！&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— 相信做技术的朋友都有“我在UE中做的XXX得意功能修改，做到一半被Epic官方‘疯狂殴打’的经历“吧？&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;   &lt;strong&gt;先说结论：TA会一直存在。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;但是我们不妨先来看看认为   &lt;strong&gt;“TA会消失”&lt;/strong&gt;的观点。有一些观点是：&lt;/p&gt;  &lt;p&gt;技术会扩散，中间件工具会提升，商业团队的加入会使工具设计的竞争加剧，这些因素可能导致一些TA工作的核心技术未来变成普世技术。&lt;/p&gt;  &lt;p&gt;而专精这些技术的TA则会面临被正规军挤压的被动局面。相信做技术的朋友都有   &lt;strong&gt;“我在UE中做的XXX得意功能修改，做到一半被Epic官方‘疯狂殴打’”&lt;/strong&gt;的经历吧。&lt;/p&gt;  &lt;p&gt;在这里和大家分享几个“被殴打”经历。   &lt;br /&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='225' height='225'&gt;&lt;/svg&gt;" width="225"&gt;&lt;/img&gt;  &lt;h2&gt;经历一：UE里的Rig流程&lt;/h2&gt;  &lt;p&gt;为了能在引擎里制作出骨骼控制的效果，我们把动画师的结算器移进Runtime里，开发了一套在UE里的Rig流程。&lt;/p&gt;  &lt;p&gt;但是由于路线和UE的RoadMap重合，UE搞出了一个更完善的方案。这个方案比我们自研的方案更加完善，而且已经落地到了实际项目里，还提供了完整编辑器。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='554' height='175'&gt;&lt;/svg&gt;" width="554"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;对官方功能感兴趣的同学看这里：&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://link.zhihu.com/?target=https%3A//docs.unrealengine.com/en-US/Engine/Animation/ControlRig/index.html" rel="nofollow noreferrer" target="_blank"&gt;https://docs.unrealengine.com/en-US/Engine/Animation/ControlRig/index.html&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;经历二：玻璃的Shading模型&lt;/h2&gt;  &lt;p&gt;因为以前UE里没有专门为玻璃开发着色模型，所以我们自己研发了一套玻璃着色模型。   &lt;br /&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='248' height='208'&gt;&lt;/svg&gt;" width="248"&gt;&lt;/img&gt;我们自己研发的玻璃着色模型效果图  &lt;p&gt;但是UE在4.25版本推出了一套更牛的。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='359' height='186'&gt;&lt;/svg&gt;" width="359"&gt;&lt;/img&gt;UE 4.2版本推出的玻璃找色模型效果图  &lt;p&gt;不过欣慰的是，我们的方案适用于移动端，而UE的方案是针对非移动设备的。&lt;/p&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='554' height='317'&gt;&lt;/svg&gt;" width="554"&gt;&lt;/img&gt;UE内嵌体积光效果图  &lt;p&gt;之前版本的UE没有体积光，所以我们自己实现了一下。但是过了几个版本以后，UE出了内嵌在引擎内部的原生方案，并且和原生渲染、材质系统整合地非常完美。&lt;/p&gt;  &lt;h2&gt;经历四：大气&lt;/h2&gt;  &lt;p&gt;之前为了在手机上做TOD（time of day系统的缩写，指24小时的实时天气变化），所以花费了巨量时间实现了一套大气，UE在4.24版本的时候出了一套更牛的。&lt;/p&gt;  &lt;p&gt;在最新的4.25版本上，Epic官方出了它的改进型，使用了更完善的物理模型。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='554' height='289'&gt;&lt;/svg&gt;" width="554"&gt;&lt;/img&gt;UE推出的改进型大气  &lt;p&gt;以上就是一些被官方吊打的经历......&lt;/p&gt;  &lt;p&gt;TA的核心工作内容，无非就是解决美术出现的问题，或者调和美术和技术之间的配合。但是因为TA具备技术特征，所以大部分时间往往是通过技术手段，以技术的方式解决。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;于是也出现了另一种看衰TA的观点&lt;/strong&gt;：在纯技术的竞争中，掌握更多技能的TA往往会在单点技术的竞争中落入下风。随着程序技术的细分，TA最终会变成各种单项专精程序，TA只是一个过渡状态。&lt;/p&gt;  &lt;p&gt;但是一般来说，普通单一工作室也很难凑齐这么多细分TA。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1269' height='781'&gt;&lt;/svg&gt;" width="1269"&gt;&lt;/img&gt;图片来源网络  &lt;p&gt;现实情况真的是这样吗？普通人是有能力和精力上限的，目前职场对TA的理解多多少少有神话的成分。人们往往都理解TA为“技术大牛”，所以总是用他们对技术的了解，来判断其竞争力。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;然而这样的理解，多少对TA工作的认知有些以点盖面了，从而也忽略了TA的一些闪光点：&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;TA因为理解技术和美术流程，所以具备全局视野；&lt;/li&gt;   &lt;li&gt;TA在团队间的灰色区域能充当了解，弥补和沟通的角色。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;strong&gt;换言之，TA解决问题并不一定需要技术手段。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;TA可以通过技术视野，对团队面临的问题，做出更好的全局判断，及时调整工作流程，对组织架构进行调整建议，为团队做出贡献。&lt;/p&gt;  &lt;p&gt;技术都是共通的，但是驾驭技术的团队各有不同。TA通过对团队的理解，选择适合团队的技术，也是创造价值的方向。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;总的来说，TA的使命，是根据自己的视野和判断力，沟通能力，技术能力，把团队盘活。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;因为拥有更广的视野，所以能够更高效、更彻底地解决其他职业不能解决的问题 —— 这是TA的核心竞争力。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;TA更多服务于大型商业游戏团队。   &lt;strong&gt;那么在这种大规模、高技术、高复杂度的开发团队中，TA具体应该扮演什么样的角色呢？&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;TA承担的任务是探索边界，然后以决策能力、沟通能力、技术能力将边界整理成工具/流程，然后将其融入到群体智慧当中。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1200' height='675'&gt;&lt;/svg&gt;" width="1200"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;   &lt;strong&gt;在这里也举一个工作案例。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;有一次我们接到一个任务，目标是   &lt;strong&gt;在手机设备中，做出AAA级别的渲染品质&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;这是一个画面驱动的长期任务，TA承担起了   &lt;strong&gt;拆分任务&lt;/strong&gt;的角色。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;首先我们要解决是对技术进行评估。&lt;/strong&gt;移动设备的算力并不能支撑所有的PC特性，我们需要对PC特性进行评估。&lt;/p&gt;  &lt;p&gt;在TA看来，玩家心目中的画面品质分为三类：   &lt;strong&gt;艺术品质&lt;/strong&gt;、   &lt;strong&gt;着色品质&lt;/strong&gt;和   &lt;strong&gt;预计算品质&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;在   &lt;strong&gt;艺术品质&lt;/strong&gt;方面，TA需要为美术准备完善且稳定的输入输出环境，让他们能够尽可能控制画面。这里我们毫无疑问地选择了基于物理的渲染。&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;预计算品质&lt;/strong&gt;方面，我们也尽可能对预计算内容、精度、储存和编码预计算方法进行了研究，最终达成了技术闭环，实现与美术工作环境的解耦。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;我们最终落地了一个方案，让美术以离线渲染的方式制作资源。我们的预计算工具会把渲染结果当作高维信号编码，以数据驱动的方式在不同固定的算力平台上进行信号还原。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;当然，这些东西只是各种技术的原型，他们运行起来速度缓慢，工具使用也非常难受，众多的bug和渲染上的各种trick，也很难融合到我们目前的渲染引擎中。&lt;/p&gt;  &lt;p&gt;于是我们邀请   &lt;strong&gt;专业图形程序&lt;/strong&gt;将各种trick转换为有理论基础支撑的算法，并且落地优化成性能可以接受拟合算法或者查询数据，调整渲染管线，对数据总量和计算总量进行优化。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;工具组的TA和编辑器程序&lt;/strong&gt;也加入了工作，为这些工具包上外壳，并且改为基于GPU运行的版本，速度起飞。他们也设计好了用户操作逻辑和使用体验，让美术尽可能可以无感知创作。&lt;/p&gt;  &lt;p&gt;在渲染闭环完成后，我们又邀请到了   &lt;strong&gt;公司业务支持部门&lt;/strong&gt;的同事，与他们划分好基于surface、shading 、pipeline、RHI层面的边界划分，以及规划好数据接口与扩展方案。业务支持部门的同事将我们项目定制优化的渲染合并到了他们与硬件厂商优化后的RIH层，游戏研发团队的TA也可以将他们的surface层对接进来。&lt;/p&gt;  &lt;p&gt;最终技术落地交付，   &lt;strong&gt;美术在使用几个月PC级别的渲染后，发现我们已经把它发布到手机上了，差异很小，他们并不需要为移动设备的特殊渲染重新积累经验&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;上面的案例是对TA目前工作的一瞥。   &lt;strong&gt;从分析诉求，到原型落地，到沟通划分边界，再到使用体验设计 —— 这些都是TA工作的一部分，技术只是其中一环。&lt;/strong&gt;&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='6392' height='3712'&gt;&lt;/svg&gt;" width="6392"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;在大型商业游戏中，开发的核心目标是在“增加内容”、“控制复杂度”和“降低成本”之间寻找性价比最高的中间点。&lt;/p&gt;  &lt;p&gt;也就是尽可能地使用固定成本，在团队成本增长因为边际效应，问题逐渐严重的情况下，降低增长曲线，从而达到更高的内容产出，让游戏在商业竞争中脱颖而出。&lt;/p&gt;  &lt;p&gt;面对这个开发目标，TA首当其冲，站在第一线，管理游戏开发成本最高的美术生产管线，以及承担美术和技术间的协调工作。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;TA是团队协同的必然结果。&lt;/strong&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— 只要游戏开发和DCC工具侧没有形成大而全的制作流水线，只要渲染效果、美术制作流程还有优化和个性表达的自由度存在，TA这个岗位就会伴随项目研发需求长期存在。&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;游戏的视觉画面、交互内容需要持续带给玩家新鲜感。硬件设备的更新换代给游戏画面品质的升级提供了基础保障，也增加了游戏内容的制作量。&lt;/p&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='3840' height='2160'&gt;&lt;/svg&gt;" width="3840"&gt;&lt;/img&gt;《死亡搁浅》  &lt;p&gt;技术分享、踩坑日志就像是《死亡搁浅》里线上玩家留给彼此的印迹。在可预期的未来，TA仍然会继续带着跨界美术和程序的技能点，沿着不同的技术路线进行快速验证。&lt;/p&gt;  &lt;p&gt;现阶段，具体到某一个研发团队，TA的工作内容可能受到团队人员组成和团队规模因素影响，进行更为细分的职责分割，例如专注于动画、特效、UI、程序化生成等的TA职位。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;但也有一部分技能树是通用的，包括但不限于以下几点：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;1.根据技术需求和团队构成，灵活发挥自身能力，把业界前沿技术融会贯通，提取能够协助项目研发的技术，将其融入工作流程，实现此前无法达成或代价过高的渲染效果。这个过程包括但不限于对前沿学术成果的验证，评估其他开发者分享的技术方案是否适用于项目，和构思独有的解决方案等；&lt;/p&gt;  &lt;p&gt;2.优化美术制作流程，深入了解美术流程中的痛点，将枯燥繁复的操作总结为可程序化处理的解决流程，扩展开发引擎和建模软件，提升美术工作效率；&lt;/p&gt;  &lt;p&gt;3.与程序、美术配合，在研发流程的全过程中都能够灵活应对性能与效果的平衡问题，多途径优化游戏运行性能；&lt;/p&gt;  &lt;p&gt;4.要对于开发流程、美术制作中的各个步骤有宏观的认识，遇到技术难题可以及时定位问题发生的点，保持和美术、程序的顺畅沟通，共同协调解决。&lt;/p&gt;  &lt;p&gt;这是个极为灵活多变的岗位，   &lt;strong&gt;随着硬件设备的飞速发展，游戏研发技术的更新迭代，可能会改变的是TA所研究的工具链和团队中的分工定位，不变的是对于游戏研发中的新需求进行持续的跨学科支持&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;只要游戏开发和DCC工具侧没有形成大而全的制作流水线，只要渲染效果、美术制作流程还有优化和个性表达的自由度存在，TA这个岗位就会伴随项目研发需求长期存在。&lt;/strong&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— 经验丰富的那种TA啊，越老越吃香！&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;就当下而言，TA的存在是必然的。国内的游戏行业还处于发展中阶段，还需要各类人才参与。&lt;/p&gt;  &lt;p&gt;近几年，国内游戏行业对TA越来越重视，近段时间对这个岗位的需求量也会比较高（特别是经验丰富的那种TA，越老越吃香？）。&lt;/p&gt;  &lt;p&gt;目前国内TA大部分偏向于shader这一块。但在欧美国家，TA已经相对精细化，会分为动画TA、灯光TA、材质TA、特效TA等，各个模块都有专人进行深入研究。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='2400' height='1350'&gt;&lt;/svg&gt;" width="2400"&gt;&lt;/img&gt;图片来源Unreal Engine官网  &lt;p&gt;还有目前各个高校也开设了游戏制作专业，这个和2000年左右计算机专业崛起也有异曲同工之妙：这两个专业在初期都想占领制高点，但由于缺乏对行业的深入了解和未来趋势的判断，导致专业设置会更偏向程序、美术、数值和策划等一些明显的职位。&lt;/p&gt;  &lt;p&gt;相比这些明显的职位，TA就低调很多，以至于被忽略。当各个游戏公司开始重视TA，并开始大量招聘TA的时候，整条人才生产链的建立 —— 从高校招聘相关老师，到设置相关专业，再到正规军正式投入生产 —— 都需要时间。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;这将会导致TA人才的稀缺，也就是说，至少十几年内，这个职位还是很有市场的。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;随着科技的发展，软件会越来越成熟并且智能化，尤其是以后AI的介入，会对各类职业造成很大的影响。&lt;/p&gt;  &lt;p&gt;到了那个时候，只要制作人员吼一声“我要XX工具”，AI就会调集全世界的云上资源，然后送到你手上。&lt;/p&gt;  &lt;p&gt;此时的TA也许会变异，成功转职成另外一种职业，who knows：）&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='725' height='113'&gt;&lt;/svg&gt;" width="725"&gt;&lt;/img&gt;  &lt;blockquote&gt;   &lt;strong&gt;—— TA是程序、美术、策划之间的桥梁，连接彼此，减少沟通成本，加强沟通效率。&lt;/strong&gt;&lt;/blockquote&gt;  &lt;p&gt;游戏技术美术是一个比较全能的职位。&lt;/p&gt;  &lt;p&gt;现在很多TA都是由程序或美术转职而来，他们需要对游戏流程和性能等每个模块都熟悉掌握，然后再从个人自身优势出发，去做的自己最擅长的方向。&lt;/p&gt;  &lt;p&gt;国外大厂通常会把这个职位分得很细，分类包括材质TA、动画TA、特效TA、渲染TA和灯光TA等等。我们公司从2019底年才开设技术美术这个职业通道，还没有细分方向。就TA这个职位而言，我认为最基本可以分为技术方向TA和美术方向TA。&lt;/p&gt;  &lt;p&gt;此外，一名合格的TA一定要有强大的耐心去面对程序、美术和策划之间的“代沟”。在游戏行业工作过的大家应该都知道，程序、美术、策划这三大类同事在日常沟通中经常会各种“撕”。但三者缺一不可，彼此又爱又恨。&lt;/p&gt;  &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='299' height='168'&gt;&lt;/svg&gt;" width="299"&gt;&lt;/img&gt;  &lt;p&gt;然后技术美术诞生了！TA刚好可以去做三者之间的桥梁，连接彼此，减少沟通成本，加强沟通效率。TA需要理解美术对工具的诉求，也要有一定的美术审美能力，以及对先进技术的学习能力。&lt;/p&gt;  &lt;p&gt;所以我认为技术美术会是一个长期存在的职业，并且发展会越来越细分，而且将会有越来越多的美术或程序同学转型到这一职业。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;以上就是六位天美TA同事的分享，请各位多多指教。如果大家对TA这个岗位有任何看法，也请在评论区留言，我们期待与你交流 (~￣▽￣)~[]&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>zhihu</category>
      <guid isPermaLink="true">https://itindex.net/detail/62578-%E6%8A%80%E6%9C%AF-%E7%BE%8E%E6%9C%AF-%E5%AD%98%E5%9C%A8</guid>
      <pubDate>Mon, 09 Jan 2023 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>如何轻松做数据治理？开源技术栈告诉你答案</title>
      <link>https://itindex.net/detail/62561-%E6%95%B0%E6%8D%AE-%E6%B2%BB%E7%90%86-%E5%BC%80%E6%BA%90</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img alt="data-lineage" height="293" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/0.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;搭建一套数据治理体系耗时耗力，但或许我们没有必要从头开始搞自己的数据血缘项目。本文分享如何用开源、现代的 DataOps、ETL、Dashboard、元数据、数据血缘管理系统构建大数据治理基础设施。&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian#h-1" name="h-1"&gt;&lt;/a&gt;元数据治理系统&lt;/h2&gt;  &lt;p&gt;   &lt;strong&gt;元数据治理&lt;/strong&gt;系统是一个提供了所有数据在哪、格式化方式、生成、转换、依赖、呈现和所属的   &lt;strong&gt;一站式视图&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;元数据治理系统是所有数据仓库、数据库、表、仪表板、ETL 作业等的   &lt;strong&gt;目录接口&lt;/strong&gt;（catalog），有了它，我们就不用在群里喊“大家好，我可以更改这个表的 schema 吗？”、 “请问谁知道我如何找到 table-view-foo-bar 的原始数据？”…一个成熟的数据治理方案中的元数据治理系统，对数据团队来说非常必要。&lt;/p&gt;  &lt;p&gt;而   &lt;strong&gt;数据血缘&lt;/strong&gt;则是元数据治理系统众多需要管理的元数据之一，例如，某些 Dashboard 是某一个 Table View 的下游，而这个 Table View 又是从另外两个上游表 JOIN 而来。显然，应该清晰地掌握、管理这些信息，去构建一个可信、可控的系统和数据质量控制体系。&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian#h-2" name="h-2"&gt;&lt;/a&gt;数据治理的可行方案&lt;/h2&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-3" name="h-3"&gt;&lt;/a&gt;数据治理方案设计&lt;/h3&gt;  &lt;p&gt;元数据和数据血缘本质上非常适合采用图数据建模、图数据库。因为数据治理涉及的典型查询便是面向图关系的查询，像“查找指定组件（即表）的所有 n 度（深度）的数据血缘”就是图查询语句   &lt;code&gt;FIND ALL PATH&lt;/code&gt;跑起来的事。从日常大家在   &lt;a href="http://c.nxw.so/aMIsW"&gt;论坛&lt;/a&gt;、微信群里讨论的查询和图建模来看，NebulaGraph 社区很多人在从零开始搭建数据血缘系统，而这些工作看起来大多是在重复造轮子，而且还是不容易造的轮子。&lt;/p&gt;  &lt;p&gt;既然如此，前人种树后人乘凉，这里我决定搭建一个完备、端到端（不只有元数据管理）的数据系统，供大家参考解决数据血缘、数据治理问题。这个套数据系统会采用市面上优秀的开源项目，而图数据库这块还是采用大家的老朋友——NebulaGraph。希望对大家能有所启发，在此基础之上拥有一个相对完善的图模型，以及设计精巧、开箱即用的元数据治理系统。&lt;/p&gt;  &lt;p&gt;下面，来看看元数据治理系统的轮子都需要哪些功能组件：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;元数据抽取    &lt;ul&gt;     &lt;li&gt;这部分需要从不同的数据栈拉/推数据，像是从数据库、数仓、Dashboard，甚至是 ETL Pipeline 和应用、服务中搞数据。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;元数据存储    &lt;ul&gt;     &lt;li&gt;可以存在数据库、图数据库里，甚至存成超大的 JSON manifest 文件都行&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;元数据目录接口系统 Catalog    &lt;ul&gt;     &lt;li&gt;提供 API / GUI 来读写元数据和数据血缘系统&lt;/li&gt;&lt;/ul&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="diagram-of-ref-project" height="499" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/1.svg" width="315"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-4" name="h-4"&gt;&lt;/a&gt;开源技术栈&lt;/h3&gt;  &lt;p&gt;下面，介绍下数据治理系统的每个部分。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#h-5" name="h-5"&gt;&lt;/a&gt;数据库和数仓&lt;/h4&gt;  &lt;p&gt;为了处理和使用原始和中间数据，这里一定涉及至少一个数据库或者数仓。它可以是 Hive、Apache Delta、TiDB、Cassandra、MySQL 或 Postgres。&lt;/p&gt;  &lt;p&gt;在这个参考项目中，我们选一个简单、流行的 Postgres。&lt;/p&gt;  &lt;p&gt;✓ 数据仓库：Postgres&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#dataops-6" name="dataops-6"&gt;&lt;/a&gt;数据运维 DataOps&lt;/h4&gt;  &lt;p&gt;我们应该有某种 DataOps 的方案，让 Pipeline 和环境具有可重复性、可测试性和版本控制性。&lt;/p&gt;  &lt;p&gt;在这里，我们使用了 GitLab 创建的   &lt;a href="https://gitlab.com/meltano/meltano"&gt;Meltano&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;Meltano 是一个 just-work 的 DataOps 平台，它可以用巧妙且优雅的方式将   &lt;a href="https://singer.io/"&gt;Singer&lt;/a&gt;作为 EL 和   &lt;a href="https://getdbt.com/"&gt;dbt&lt;/a&gt;作为 T 连接起来。此外，它还连接到其他一些 dataInfra 实用程序，例如 Apache Superset 和 Apache Airflow 等。&lt;/p&gt;  &lt;p&gt;至此，我们又纳入了一个成员：&lt;/p&gt;  &lt;p&gt;✓ GitOps：Meltano   &lt;a href="https://gitlab.com/meltano/meltano"&gt;https://gitlab.com/meltano/meltano&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#etl-7" name="etl-7"&gt;&lt;/a&gt;ETL 工具&lt;/h4&gt;  &lt;p&gt;上面我们提到过组合   &lt;a href="https://singer.io/"&gt;Singer&lt;/a&gt;与 Meltano 将来自许多不同数据源的数据 E（提取）和 L（加载）数据目标，并使用   &lt;a href="https://getdbt.com/"&gt;dbt&lt;/a&gt;作为 Transform 的平台。于是我们得到：&lt;/p&gt;  &lt;p&gt;✓ EL：Singer   &lt;br /&gt;✓ T: dbt&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#h-8" name="h-8"&gt;&lt;/a&gt;数据可视化&lt;/h4&gt;  &lt;p&gt;在数据之上创建 Dashboard、图表和表格得到数据的洞察是很符合直觉的，类似大数据之上的 Excel 图标功能。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="414" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/2.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://superset.apache.org/"&gt;Apache Superset&lt;/a&gt;是我很喜欢的开源数据可视化项目，我准备用它来作为被治理管理的目标之一。同时，还会利用它实现可视化功能来完成元数据洞察。于是，&lt;/p&gt;  &lt;p&gt;✓ Dashboard：Apache Superset&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#dag-job-orchestration-9" name="dag-job-orchestration-9"&gt;&lt;/a&gt;任务编排（DAG Job Orchestration）&lt;/h4&gt;  &lt;p&gt;在大多数情况下，我们的 DataOps 作业、任务会演变成需要编排系统的规模，我们可以用   &lt;a href="https://airflow.apache.org/"&gt;Apache Airflow&lt;/a&gt;来负责这一块。&lt;/p&gt;  &lt;p&gt;✓ DAG：Apache Airflow   &lt;a href="https://airflow.apache.org/"&gt;https://airflow.apache.org/&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#h-10" name="h-10"&gt;&lt;/a&gt;元数据治理&lt;/h4&gt;  &lt;p&gt;随着越来越多的组件和数据被引入数据基础设施，在数据库、表、数据建模（schema）、Dashboard、DAG（编排系统中的有向无环图）、应用与服务的各个生命周期阶段中都将存着海量的元数据，需要对它们的管理员和团队进行协同管理、连接和发现。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://www.amundsen.io/amundsen/"&gt;Linux Foundation Amundsen&lt;/a&gt;是解决该问题的最佳项目之一。 Amundsen 用图数据库为事实源（single source of truth）以加速多跳查询，Elasticsearch 为全文搜索引擎。它在顺滑地处理所有元数据及其血缘之余，还提供了优雅的 UI 和 API。 Amundsen 支持多种图数据库为后端，这里咱们用   &lt;a href="https://nebula-graph.com.cn"&gt;NebulaGraph&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="499" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/3.png" width="581"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;现在的技术栈：&lt;/p&gt;  &lt;p&gt;✓ 数据发现：Linux Foundation Amundsen   &lt;br /&gt;✓ 全文搜索：Elasticsearch   &lt;br /&gt;✓ 图数据库：NebulaGraph&lt;/p&gt;  &lt;p&gt;好的，所有组件都齐正了，开始组装它们吧。&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian#h-11" name="h-11"&gt;&lt;/a&gt;环境搭建与各组件初识&lt;/h2&gt;  &lt;p&gt;本次实践的项目方案已开源，你可以访问   &lt;a href="https://github.com/wey-gu/data-lineage-ref-solution"&gt;https://github.com/wey-gu/data-lineage-ref-solution&lt;/a&gt;来获得对应的代码。&lt;/p&gt;  &lt;p&gt;整个实践过程，我遵循了尽量干净、鼓励共建的原则。项目预设在一个 unix-like 系统上运行，且联网和装有 Docker-Compose。&lt;/p&gt;  &lt;p&gt;这里，我将在 Ubuntu 20.04 LTS X86_64 上运行它，当然在其他发行版或 Linux 版本上应该也没有问题。&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-12" name="h-12"&gt;&lt;/a&gt;运行一个数仓、数据库&lt;/h3&gt;  &lt;p&gt;首先，安装 Postgres 作为我们的数仓。&lt;/p&gt;  &lt;p&gt;这个单行命令会创建一个使用 Docker 在后台运行的 Postgres，进程关闭之后容器不会残留而是被清理掉（因为参数   &lt;code&gt;--rm&lt;/code&gt;）。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker run --rm --name postgres \
    -e POSTGRES_PASSWORD=lineage_ref \
    -e POSTGRES_USER=lineage_ref \
    -e POSTGRES_DB=warehouse -d \
    -p 5432:5432 postgres&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;我们可以用 Postgres CLI 或 GUI 客户端来验证命令是否执行成功。&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#dataops-13" name="dataops-13"&gt;&lt;/a&gt;DataOps 工具链部署&lt;/h3&gt;  &lt;p&gt;接下来，安装有机结合了 Singer 和 dbt 的 Meltano。&lt;/p&gt;  &lt;p&gt;Meltano 帮助我们管理 ETL 工具（作为插件）及其所有配置和 pipeline。这些元信息位于 Meltano 配置及其系统数据库中，其中配置是基于文件的（可以使用 GitOps 管理），它的默认系统数据库是 SQLite。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#meltano-14" name="meltano-14"&gt;&lt;/a&gt;安装 Meltano&lt;/h4&gt;  &lt;p&gt;使用 Meltano 的工作流是启动一个“meltano 项目”并开始将 E、L 和 T 添加到配置文件中。Meltano 项目的启动只需要一个 CLI 命令   &lt;code&gt;meltano init yourprojectname&lt;/code&gt;。不过，在那之前，先用 Python 的包管理器 pip 或者 Docker 镜像安装 Meltano，像我示范的这样：&lt;/p&gt;  &lt;p&gt;在 Python 虚拟环境中使用 pip 安装 Meltano：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;mkdir .venv
# example in a debian flavor Linux distro
sudo apt-get install python3-dev python3-pip python3-venv python3-wheel -y
python3 -m venv .venv/meltano
source .venv/meltano/bin/activate
python3 -m pip install wheel
python3 -m pip install meltano

# init a project
mkdir meltano_projects &amp;amp;&amp;amp; cd meltano_projects
# replace &amp;lt;yourprojectname&amp;gt; with your own one
touch .env
meltano init &amp;lt;yourprojectname&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;或者，用 Docker 容器安装 Meltano：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker pull meltano/meltano:latest
docker run --rm meltano/meltano --version

# init a project
mkdir meltano_projects &amp;amp;&amp;amp; cd meltano_projects

# replace &amp;lt;yourprojectname&amp;gt; with your own one
touch .env
docker run --rm -v &amp;quot;$(pwd)&amp;quot;:/projects \
             -w /projects --env-file .env \
             meltano/meltano init &amp;lt;yourprojectname&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;除了知晓   &lt;code&gt;meltano init&lt;/code&gt;之外，最好掌握 Meltano 部分命令，例如   &lt;code&gt;meltano etl&lt;/code&gt;表示 ETL 的执行，   &lt;code&gt;meltano invoke &amp;lt;plugin&amp;gt;&lt;/code&gt;来调用插件命令。详细可以参考它的速查表   &lt;a href="https://docs.meltano.com/reference/command-line-interface"&gt;https://docs.meltano.com/reference/command-line-interface&lt;/a&gt;。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#meltano-gui-15" name="meltano-gui-15"&gt;&lt;/a&gt;Meltano GUI 界面&lt;/h4&gt;  &lt;p&gt;Meltano 自带一个基于 Web 的 UI，执行   &lt;code&gt;ui&lt;/code&gt;子命令就能启动它：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;meltano ui&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;它默认会跑在   &lt;code&gt;http://localhost:5000&lt;/code&gt;上。&lt;/p&gt;  &lt;p&gt;针对 Docker 的运行环境，在暴露 5000 端口的情况下运行容器即可。由于容器的默认命令已经是   &lt;code&gt;meltano ui&lt;/code&gt;，所以   &lt;code&gt;run&lt;/code&gt;的命令只需：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker run -v &amp;quot;$(pwd)&amp;quot;:/project \
             -w /project \
             -p 5000:5000 \
             meltano/meltano&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#meltano-16" name="meltano-16"&gt;&lt;/a&gt;Meltano 项目示例&lt;/h4&gt;  &lt;p&gt;GitHub 用户   &lt;a href="https://github.com/pnadolny13"&gt;Pat Nadolny&lt;/a&gt;在开源项目   &lt;a href="https://github.com/pnadolny13/meltano_example_implementations/tree/main/meltano_projects/"&gt;singer_dbt_jaffle&lt;/a&gt;中做了很好的示例。他采用 dbt 的 Meltano 示例数据集，利用 Airflow   &lt;a href="https://github.com/pnadolny13/meltano_example_implementations/tree/main/meltano_projects/dbt_orchestration"&gt;编排 ETL 任务&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;不只这样，他还有利用 Superset 的例子，见   &lt;a href="https://github.com/pnadolny13/meltano_example_implementations/tree/main/meltano_projects/jaffle_superset"&gt;jaffle_superset&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;前人种树我们来吃果，按照 Pat Nadolny 的实践，我们可以这样地运行数据管道（pipeline）：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;a href="https://hub.meltano.com/taps/csv"&gt;tap-CSV&lt;/a&gt;（Singer）从 CSV 文件中提取数据&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://hub.meltano.com/targets/postgres"&gt;target-postgres&lt;/a&gt;（Singer） 将数据加载到 Postgres&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://hub.meltano.com/transformers/dbt"&gt;dbt&lt;/a&gt;将数据转换为聚合表或视图&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;注意，上面我们已经启动了 Postgres，可以跳过容器启动 Postgres 这步。&lt;/p&gt;  &lt;p&gt;操作过程是：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;git clone https://github.com/pnadolny13/meltano_example_implementations.git
cd meltano_example_implementations/meltano_projects/singer_dbt_jaffle/

meltano install
touch .env
echo PG_PASSWORD=&amp;quot;lineage_ref&amp;quot; &amp;gt;&amp;gt; .env
echo PG_USERNAME=&amp;quot;lineage_ref&amp;quot; &amp;gt;&amp;gt; .env

# Extract and Load(with Singer)
meltano run tap-csv target-postgres

# Trasnform(with dbt)
meltano run dbt:run

# Generate dbt docs
meltano invoke dbt docs generate

# Serve generated dbt docs
meltano invoke dbt docs to serve

# Then visit http://localhost:8080&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;现在，我们可以连接到 Postgres 来查看加载和转换后的数据预览。如下所示，截图来自 VS Code 的 SQLTool。&lt;/p&gt;  &lt;p&gt;payments 表里长这样子：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="481" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/4.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#bi-dashboard-17" name="bi-dashboard-17"&gt;&lt;/a&gt;搭一个 BI Dashboard 系统&lt;/h3&gt;  &lt;p&gt;现在，我们的数据仓库有数据了。接下来，可以试着用下这些数据。&lt;/p&gt;  &lt;p&gt;像仪表盘 Dashbaord 这样的 BI 工具能帮我们从数据中获得有用的洞察。使用可视化工具 Apache Superset 可以很容易地创建和管理这些基于数据源的 Dashboard 和各式各样的图表。&lt;/p&gt;  &lt;p&gt;本章的重点不在于 Apache Superset 本身，所以，咱们还是复用 Pat Nadolny 的   &lt;a href="https://github.com/pnadolny13/meltano_example_implementations/tree/main/meltano_projects/jaffle_superset"&gt;jaffle_superset&lt;/a&gt;例子。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#bootstrap-meltano-superset-18" name="bootstrap-meltano-superset-18"&gt;&lt;/a&gt;Bootstrap Meltano 和 Superset&lt;/h4&gt;  &lt;p&gt;创建一个安装了 Meltano 的 Python VENV：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;mkdir .venv
python3 -m venv .venv/meltano
source .venv/meltano/bin/activate
python3 -m pip install wheel
python3 -m pip install meltano&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;参考 Pat 的小抄，做一些细微的调整：&lt;/p&gt;  &lt;p&gt;克隆 repo，进入   &lt;code&gt;jaffle_superset&lt;/code&gt;项目：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;git clone https://github.com/pnadolny13/meltano_example_implementations.git
cd meltano_example_implementations/meltano_projects/jaffle_superset/&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;修改 meltano 配置文件，让 Superset 连接到我们创建的 Postgres：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;vim meltano_projects/jaffle_superset/meltano.yml&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这里，我将主机名更改为“10.1.1.111”，这是我当前主机的 IP。如果你是 Windows 或者 macOS 上的 Docker Desktop，这里不要修改主机名，否则就要和我一样手动改成实际地址：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;--- a/meltano_projects/jaffle_superset/meltano.yml
+++ b/meltano_projects/jaffle_superset/meltano.yml
@@ -71,7 +71,7 @@ plugins:
               A list of database driver dependencies can be found here https://superset.apache.org/docs/databases/installing-database-drivers
     config:
       database_name: my_postgres
-      sqlalchemy_uri: postgresql+psycopg2://${PG_USERNAME}:${PG_PASSWORD}@host.docker.internal:${PG_PORT}/${PG_DATABASE}
+      sqlalchemy_uri: postgresql+psycopg2://${PG_USERNAME}:${PG_PASSWORD}@10.1.1.168:${PG_PORT}/${PG_DATABASE}
       tables:
       - model.my_meltano_project.customers
       - model.my_meltano_project.orders&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;添加 Postgres 登录的信息到   &lt;code&gt;.env&lt;/code&gt;文件：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;echo PG_USERNAME=lineage_ref &amp;gt;&amp;gt; .env
echo PG_PASSWORD=lineage_ref &amp;gt;&amp;gt; .env&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;安装 Meltano 项目，运行 ETL 任务：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;meltano install
meltano run tap-csv target-postgres dbt:run&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;调用、启动 Superset，这里注意   &lt;code&gt;ui&lt;/code&gt;不是 meltano 的内部命令，而是一个配置进去的自定义行为（user-defined action）：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;meltano invoke superset:ui&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;在另一个命令行终端执行自定义的命令   &lt;code&gt;load_datasources&lt;/code&gt;：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;meltano invoke superset:load_datasources&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;通过浏览器访问   &lt;code&gt;http://localhost:8088/&lt;/code&gt;就是 Superset 的图形界面了：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="486" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/5.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#dashboard-19" name="dashboard-19"&gt;&lt;/a&gt;创建一个 Dashboard&lt;/h4&gt;  &lt;p&gt;现在，我们站在 Meltano、Postgres 的肩膀上，用 ETL 数据建一个 Dashboard 吧：&lt;/p&gt;  &lt;p&gt;点击   &lt;code&gt;+ DASHBOARD&lt;/code&gt;，填写仪表盘名称，再先后点击   &lt;code&gt;SAVE&lt;/code&gt;和   &lt;code&gt;+ CREATE A NEW CHART&lt;/code&gt;：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="486" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/6.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在新图表（Create a new chart）视图中，选择图表类型和数据集。在这里，我选择了   &lt;code&gt;orders&lt;/code&gt;表作为数据源和   &lt;code&gt;Pie Chart&lt;/code&gt;图表类型：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="500" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/7.png" width="652"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点击   &lt;code&gt;CREATE NEW CHART&lt;/code&gt;后，在图表定义视图中选择 “status” 的 “Query” 为 “DIMENSIONS”，“COUNT(amount)” 为 “METRIC”。至此，咱们就可以看到每个订单状态分布的饼图了。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="500" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/8.png" width="652"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点击   &lt;code&gt;SAVE&lt;/code&gt;，系统会询问应该将此图表添加到哪个 Dashboard。选择后，单击   &lt;code&gt;SAVE &amp;amp; GO TO DASHBOARD&lt;/code&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="500" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/9.png" width="652"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在 Dashboard 中，我们可以看到所有的图表。这不，你可以看到我额外添加的、用来显示客户订单数量分布的图表：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="493" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/10.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点   &lt;code&gt;···&lt;/code&gt;能看到刷新率设置、下载渲染图等其他的功能。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="493" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/11.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;现在，我们有一个简单但典型的 HomeLAB 数据技术栈了，并且所有东西都是开源的！&lt;/p&gt;  &lt;p&gt;想象一下，我们在 CSV 中有 100 个数据集，在数据仓库中有 200 个表，并且有几个数据工程师在运行不同的项目，这些项目使用、生成不同的应用与服务、Dashbaord 和数据库。当有人想要查找、发现或者修改其中的一些表、数据集、Dashbaord 和管道，在沟通和工程方面可能都是非常不好管理的。&lt;/p&gt;  &lt;p&gt;上面我们提到，这个示例项目的   &lt;strong&gt;主要功能是元数据发现系统&lt;/strong&gt;。&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-20" name="h-20"&gt;&lt;/a&gt;元数据发现系统&lt;/h3&gt;  &lt;p&gt;现在，需要我们部署一个带有 NebulaGraph 和 Elasticsearch 的 Amundsen。有了 Amundsen，我们可以在一个地方发现和管理整个数据栈中的所有元数据。&lt;/p&gt;  &lt;p&gt;Amundsen 主要有两个部分组成：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;元数据导入 Metadata Ingestion    &lt;ul&gt;     &lt;li&gt;      &lt;a href="https://www.amundsen.io/amundsen/databuilder/"&gt;Amundsen Databuilder&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;元数据目录服务 Metadata Catalog    &lt;ul&gt;     &lt;li&gt;      &lt;a href="https://www.amundsen.io/amundsen/frontend/"&gt;Amundsen Frontend Service&lt;/a&gt;&lt;/li&gt;     &lt;li&gt;      &lt;a href="https://www.amundsen.io/amundsen/metadata/"&gt;Amundsen Metadata Service&lt;/a&gt;&lt;/li&gt;     &lt;li&gt;      &lt;a href="https://www.amundsen.io/amundsen/search/"&gt;Amundsen Search Service&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;它的工作原理是：利用   &lt;code&gt;Databuilder&lt;/code&gt;提取不同数据源的元数据，并将元数据持久化到   &lt;code&gt;Metadata Service&lt;/code&gt;和   &lt;code&gt;Search Service&lt;/code&gt;中，用户从   &lt;code&gt;Frontend Service&lt;/code&gt;或   &lt;code&gt;Metadata Service&lt;/code&gt;的 API 获取数据。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#amundsen-21" name="amundsen-21"&gt;&lt;/a&gt;部署 Amundsen&lt;/h4&gt;  &lt;h5&gt;   &lt;a href="https://itindex.net/relian#metadata-service-22" name="metadata-service-22"&gt;&lt;/a&gt;元数据服务 Metadata Service&lt;/h5&gt;  &lt;p&gt;我们用 docker-compose 部署一个 Amundsen 集群。由于 Amundsen 对 NebulaGraph 后端的支持   &lt;a href="https://github.com/amundsen-io/amundsen/pull/1817"&gt;pr#1817&lt;/a&gt;尚未合并，还不能用官方的代码。这里，先用我的 fork 版本。&lt;/p&gt;  &lt;p&gt;先克隆包含所有子模块的 repo：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;git clone -b amundsen_nebula_graph --recursive git@github.com:wey-gu/amundsen.git
cd amundsen&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;启动所有目录服务 catalog services 及其后端存储：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker-compose -f docker-amundsen-nebula.yml up&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;由于这个 docker-compose 文件是供开发人员试玩、调试 Amundsen 用的，而不是给生产部署准备的，它在启动的时候会从代码库构建镜像，第一次跑的时候启动会慢一些。&lt;/p&gt;  &lt;p&gt;部署好了之后，我们使用 Databuilder 将一些示例、虚构的数据加载存储里。&lt;/p&gt;  &lt;h5&gt;   &lt;a href="https://itindex.net/relian#databuilder-23" name="databuilder-23"&gt;&lt;/a&gt;抓取元数据 Databuilder&lt;/h5&gt;  &lt;p&gt;Amundsen Databuilder 就像 Meltano 系统一样，只不过是用在元数据的上的 ETL ，它把元数据加载到   &lt;code&gt;Metadata Service&lt;/code&gt;和   &lt;code&gt;Search Service&lt;/code&gt;的后端存储：NebulaGraph 和 Elasticsearch 里。这里的 Databuilder 只是一个 Python 模块，所有的元数据 ETL 作业可以作为脚本运行，也可以用 Apache Airflow 等 DAG 平台进行编排。&lt;/p&gt;  &lt;p&gt;安装   &lt;a href="https://github.com/amundsen-io/amundsen/tree/main/databuilder"&gt;Amundsen Databuilder&lt;/a&gt;：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;cd databuilder
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install wheel
python3 -m pip install -r requirements.txt
python3 setup.py install&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;调用这个示例数据构建器 ETL 脚本来把示例的虚拟数据导进去。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;python3 example/scripts/sample_data_loader_nebula.py&lt;/code&gt;&lt;/pre&gt;  &lt;h5&gt;   &lt;a href="https://itindex.net/relian#amundsen-24" name="amundsen-24"&gt;&lt;/a&gt;验证一下 Amundsen&lt;/h5&gt;  &lt;p&gt;在访问 Amundsen 之前，我们需要创建一个测试用户：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# run a container with curl attached to amundsenfrontend
docker run -it --rm --net container:amundsenfrontend nicolaka/netshoot

# Create a user with id test_user_id
curl -X PUT -v http://amundsenmetadata:5002/user \
    -H &amp;quot;Content-Type: application/json&amp;quot; \
    --data \
    &amp;apos;{&amp;quot;user_id&amp;quot;:&amp;quot;test_user_id&amp;quot;,&amp;quot;first_name&amp;quot;:&amp;quot;test&amp;quot;,&amp;quot;last_name&amp;quot;:&amp;quot;user&amp;quot;, &amp;quot;email&amp;quot;:&amp;quot;test_user_id@mail.com&amp;quot;}&amp;apos;

exit&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;然后我们可以在   &lt;code&gt;http://localhost:5000&lt;/code&gt;查看 UI 并尝试搜索   &lt;code&gt;test&lt;/code&gt;，它应该会返回一些结果。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="306" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/12.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;然后，可以单击并浏览在   &lt;code&gt;sample_data_loader_nebula.py&lt;/code&gt;期间加载到 Amundsen 的那些示例元数据。&lt;/p&gt;  &lt;p&gt;此外，我们还可以通过 NebulaGraph Studio 的地址   &lt;code&gt;http://localhost:7001&lt;/code&gt;访问 NebulaGraph 里的这些数据。&lt;/p&gt;  &lt;p&gt;下图显示了有关 Amundsen 组件的更多详细信息：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;┌────────────────────────┐ ┌────────────────────────────────────────┐
       │ Frontend:5000          │ │ Metadata Sources                       │
       ├────────────────────────┤ │ ┌────────┐ ┌─────────┐ ┌─────────────┐ │
       │ Metaservice:5001       │ │ │        │ │         │ │             │ │
       │ ┌──────────────┐       │ │ │ Foo DB │ │ Bar App │ │ X Dashboard │ │
  ┌────┼─┤ Nebula Proxy │       │ │ │        │ │         │ │             │ │
  │    │ └──────────────┘       │ │ │        │ │         │ │             │ │
  │    ├────────────────────────┤ │ └────────┘ └─────┬───┘ └─────────────┘ │
┌─┼────┤ Search searvice:5002   │ │                  │                     │
│ │    └────────────────────────┘ └──────────────────┼─────────────────────┘
│ │    ┌─────────────────────────────────────────────┼───────────────────────┐
│ │    │                                             │                       │
│ │    │ Databuilder     ┌───────────────────────────┘                       │
│ │    │                 │                                                   │
│ │    │ ┌───────────────▼────────────────┐ ┌──────────────────────────────┐ │
│ │ ┌──┼─► Extractor of Sources           ├─► nebula_search_data_extractor │ │
│ │ │  │ └───────────────┬────────────────┘ └──────────────┬───────────────┘ │
│ │ │  │ ┌───────────────▼────────────────┐ ┌──────────────▼───────────────┐ │
│ │ │  │ │ Loader filesystem_csv_nebula   │ │ Loader Elastic FS loader     │ │
│ │ │  │ └───────────────┬────────────────┘ └──────────────┬───────────────┘ │
│ │ │  │ ┌───────────────▼────────────────┐ ┌──────────────▼───────────────┐ │
│ │ │  │ │ Publisher nebula_csv_publisher │ │ Publisher Elasticsearch      │ │
│ │ │  │ └───────────────┬────────────────┘ └──────────────┬───────────────┘ │
│ │ │  └─────────────────┼─────────────────────────────────┼─────────────────┘
│ │ └────────────────┐   │                                 │
│ │    ┌─────────────┼───►─────────────────────────┐ ┌─────▼─────┐
│ │    │ NebulaGraph │   │                         │ │           │
│ └────┼─────┬───────┴───┼───────────┐     ┌─────┐ │ │           │
│      │     │           │           │     │MetaD│ │ │           │
│      │ ┌───▼──┐    ┌───▼──┐    ┌───▼──┐  └─────┘ │ │           │
│ ┌────┼─►GraphD│    │GraphD│    │GraphD│          │ │           │
│ │    │ └──────┘    └──────┘    └──────┘  ┌─────┐ │ │           │
│ │    │ :9669                             │MetaD│ │ │  Elastic  │
│ │    │ ┌────────┐ ┌────────┐ ┌────────┐  └─────┘ │ │  Search   │
│ │    │ │        │ │        │ │        │          │ │  Cluster  │
│ │    │ │StorageD│ │StorageD│ │StorageD│  ┌─────┐ │ │  :9200    │
│ │    │ │        │ │        │ │        │  │MetaD│ │ │           │
│ │    │ └────────┘ └────────┘ └────────┘  └─────┘ │ │           │
│ │    ├───────────────────────────────────────────┤ │           │
│ └────┤ Nebula Studio:7001                        │ │           │
│      └───────────────────────────────────────────┘ └─────▲─────┘
└──────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian#h-25" name="h-25"&gt;&lt;/a&gt;穿针引线：元数据发现&lt;/h2&gt;  &lt;p&gt;设置好基本环境后，让我们把所有东西穿起来。还记得我们有 ELT 一些数据到 PostgreSQL 吗？&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="481" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/13.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;那么，我们如何让 Amundsen 发现这些数据和 ETL 的元数据呢？&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#postgres-26" name="postgres-26"&gt;&lt;/a&gt;提取 Postgres 元数据&lt;/h3&gt;  &lt;p&gt;我们从数据源开始：首先是 Postgres。&lt;/p&gt;  &lt;p&gt;我们为 Python3 安装 Postgres 客户端：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sudo apt-get install libpq-dev
pip3 install Psycopg2&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#postgres-etl-27" name="postgres-etl-27"&gt;&lt;/a&gt;执行 Postgres 元数据 ETL&lt;/h4&gt;  &lt;p&gt;运行一个脚本来解析 Postgres 元数据：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;export CREDENTIALS_POSTGRES_USER=lineage_ref
export CREDENTIALS_POSTGRES_PASSWORD=lineage_ref
export CREDENTIALS_POSTGRES_DATABASE=warehouse

python3 example/scripts/sample_postgres_loader_nebula.py&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;我们看看把 Postgres 元数据加载到 NebulaGraph 的示例脚本的代码，非常简单直接：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# part 1: PostgresMetadata --&amp;gt; CSV --&amp;gt; NebulaGraph
job = DefaultJob(
      conf=job_config,
      task=DefaultTask(
          extractor=PostgresMetadataExtractor(),
          loader=FsNebulaCSVLoader()),
      publisher=NebulaCsvPublisher())

...
# part 2: Metadata stored in NebulaGraph --&amp;gt; Elasticsearch
extractor = NebulaSearchDataExtractor()
task = SearchMetadatatoElasticasearchTask(extractor=extractor)

job = DefaultJob(conf=job_config, task=task)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;第一个工作路径是：   &lt;code&gt;PostgresMetadata --&amp;gt; CSV --&amp;gt; NebulaGraph&lt;/code&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;code&gt;PostgresMetadataExtractor&lt;/code&gt;用于从 Postgres 中提取元数据，可以参考文档    &lt;a href="https://www.amundsen.io/amundsen/databuilder/#postgresmetadataextractor"&gt;https://www.amundsen.io/amundsen/databuilder/#postgresmetadataextractor&lt;/a&gt;。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;FsNebulaCSVLoader&lt;/code&gt;用于将提取的数据转为 CSV 文件&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;NebulaCsvPublisher&lt;/code&gt;用于将元数据以 CSV 格式发布到 NebulaGraph&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;第二个工作路径是：   &lt;code&gt;Metadata stored in NebulaGraph --&amp;gt; Elasticsearch&lt;/code&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;code&gt;NebulaSearchDataExtractor&lt;/code&gt;用于获取存储在 NebulaGraph 中的元数据&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;SearchMetadatatoElasticasearchTask&lt;/code&gt;用于使 Elasticsearch 对元数据进行索引。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;请注意，在生产环境中，我们可以在脚本中或使用 Apache Airflow 等编排平台触发这些作业。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#postgres-28" name="postgres-28"&gt;&lt;/a&gt;验证 Postgres 中元数据的获取&lt;/h4&gt;  &lt;p&gt;搜索   &lt;code&gt;payments&lt;/code&gt;或者直接访问   &lt;code&gt;http://localhost:5000/table_detail/warehouse/postgres/public/payments&lt;/code&gt;，你可以看到我们 Postgres 的元数据，比如：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="346" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/14.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;像上面的屏幕截图一样，我们可以轻松完成元数据管理操作，如：添加标签、所有者和描述。&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#dbt-29" name="dbt-29"&gt;&lt;/a&gt;提取 dbt 元数据&lt;/h3&gt;  &lt;p&gt;其实，我们也可以从   &lt;a href="https://www.getdbt.com/"&gt;dbt&lt;/a&gt;本身提取元数据。&lt;/p&gt;  &lt;p&gt;Amundsen   &lt;a href="https://www.amundsen.io/amundsen/databuilder/#dbtextractor"&gt;DbtExtractor&lt;/a&gt;会解析   &lt;code&gt;catalog.json&lt;/code&gt;或   &lt;code&gt;manifest.json&lt;/code&gt;文件并将元数据加载到 Amundsen 存储，这里当然指的是 NebulaGraph 和 Elasticsearch。&lt;/p&gt;  &lt;p&gt;在上面的 Meltano 章节中，我们已经使用   &lt;code&gt;meltano invoke dbt docs generate&lt;/code&gt;生成了这个文件：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;14:23:15  Done.
14:23:15  Building catalog
14:23:15  Catalog written to /home/ubuntu/ref-data-lineage/meltano_example_implementations/meltano_projects/singer_dbt_jaffle/.meltano/transformers/dbt/target/catalog.json&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#dbt-etl-30" name="dbt-etl-30"&gt;&lt;/a&gt;dbt 元数据 ETL 的执行&lt;/h4&gt;  &lt;p&gt;我们试着解析示例 dbt 文件中的元数据吧：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ ls -l example/sample_data/dbt/
total 184
-rw-rw-r-- 1 w w   5320 May 15 07:17 catalog.json
-rw-rw-r-- 1 w w 177163 May 15 07:17 manifest.json&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;我写的这个示例的加载例子如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;python3 example/scripts/sample_dbt_loader_nebula.py&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;其中主要的代码如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# part 1: dbt manifest --&amp;gt; CSV --&amp;gt; NebulaGraph
job = DefaultJob(
      conf=job_config,
      task=DefaultTask(
          extractor=DbtExtractor(),
          loader=FsNebulaCSVLoader()),
      publisher=NebulaCsvPublisher())

...
# part 2: Metadata stored in NebulaGraph --&amp;gt; Elasticsearch
extractor = NebulaSearchDataExtractor()
task = SearchMetadatatoElasticasearchTask(extractor=extractor)

job = DefaultJob(conf=job_config, task=task)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;它和 Postgres 元数据 ETL 的唯一区别是   &lt;code&gt;extractor=DbtExtractor()&lt;/code&gt;，它带有以下配置以获取有关 dbt 项目的以下信息：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;数据库名称&lt;/li&gt;   &lt;li&gt;目录_json&lt;/li&gt;   &lt;li&gt;manifest_json&lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;   &lt;code&gt;job_config = ConfigFactory.from_dict({
  &amp;apos;extractor.dbt.database_name&amp;apos;: database_name,
  &amp;apos;extractor.dbt.catalog_json&amp;apos;: catalog_file_loc,  # File
  &amp;apos;extractor.dbt.manifest_json&amp;apos;: json.dumps(manifest_data),  # JSON Dumped objecy
  &amp;apos;extractor.dbt.source_url&amp;apos;: source_url})&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#dbt-31" name="dbt-31"&gt;&lt;/a&gt;验证 dbt 抓取结果&lt;/h4&gt;  &lt;p&gt;搜索   &lt;code&gt;dbt_demo&lt;/code&gt;或者直接访问   &lt;code&gt;http://localhost:5000/table_detail/dbt_demo/snowflake/public/raw_inventory_value&lt;/code&gt;，可以看到&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="341" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/15.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这里给一个小提示，其实，我们可以选择启用 DEBUG log 级别去看已发送到 Elasticsearch 和 NebulaGraph 的内容。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;- logging.basicConfig(level=logging.INFO)
+ logging.basicConfig(level=logging.DEBUG)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;或者，在 NebulaGraph Studio 中探索导入的数据：&lt;/p&gt;  &lt;p&gt;先点击   &lt;code&gt;Start with Vertices&lt;/code&gt;，并填写顶点 vid：   &lt;code&gt;snowflake://dbt_demo.public/fact_warehouse_inventory&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="348" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/16.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;我们可以看到顶点显示为粉红色的点。再让我们修改下   &lt;code&gt;Expand&lt;/code&gt;/ ”拓展“选项：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;方向：双向&lt;/li&gt;   &lt;li&gt;步数：单向、三步&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" height="351" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/17.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;并双击顶点（点），它将双向拓展 3 步：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="349" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/18.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;像截图展示的那般，在可视化之后的图数据库中，这些元数据可以很容易被查看、分析，并从中获得洞察。&lt;/p&gt;  &lt;p&gt;而且，我们在 NebulaGraph Studio 中看到的同 Amundsen 元数据服务的数据模型相呼应：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="415" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/19.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;最后，请记住我们曾利用 dbt 来转换 Meltano 中的一些数据，并且清单文件路径是   &lt;code&gt;.meltano/transformers/dbt/target/catalog.json&lt;/code&gt;，你可以尝试创建一个数据构建器作业来导入它。&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#superset-32" name="superset-32"&gt;&lt;/a&gt;提取 Superset 中的元数据&lt;/h3&gt;  &lt;p&gt;Amundsen 的 Superset Extractor 可以获取&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Dashboard 元数据抽取，见    &lt;a href="https://www.amundsen.io/amundsen/databuilder/databuilder/extractor/dashboard/apache_superset/apache_superset_metadata_extractor.py"&gt;apache_superset_metadata_extractor.py&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;图表元数据抽取，见    &lt;a href="https://www.amundsen.io/amundsen/databuilder/databuilder/extractor/dashboard/apache_superset/apache_superset_chart_extractor.py"&gt;apache_superset_chart_extractor.py&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;Superset 元素与数据源（表）的关系抽取，见    &lt;a href="https://www.amundsen.io/amundsen/databuilder/databuilder/extractor/dashboard/apache_superset/apache_superset_table_extractor.py"&gt;apache_superset_table_extractor.py&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;来，现在试试提取之前创建的 Superset Dashboard 的元数据。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#superset-etl-33" name="superset-etl-33"&gt;&lt;/a&gt;Superset 元数据 ETL 的执行&lt;/h4&gt;  &lt;p&gt;下边执行的示例 Superset 提取脚本可以获取数据并将元数据加载到 NebulaGraph 和 Elasticsearch 中。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;python3 sample_superset_data_loader_nebula.py&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;如果我们将日志记录级别设置为   &lt;code&gt;DEBUG&lt;/code&gt;，我们实际上可以看到这些中间的过程日志：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# fetching metadata from superset
DEBUG:urllib3.connectionpool:http://localhost:8088 &amp;quot;POST /api/v1/security/login HTTP/1.1&amp;quot; 200 280
INFO:databuilder.task.task:Running a task
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost:8088
DEBUG:urllib3.connectionpool:http://localhost:8088 &amp;quot;GET /api/v1/dashboard?q=(page_size:20,page:0,order_direction:desc) HTTP/1.1&amp;quot; 308 374
DEBUG:urllib3.connectionpool:http://localhost:8088 &amp;quot;GET /api/v1/dashboard/?q=(page_size:20,page:0,order_direction:desc) HTTP/1.1&amp;quot; 200 1058
...

# insert Dashboard

DEBUG:databuilder.publisher.nebula_csv_publisher:Query: INSERT VERTEX `Dashboard` (`dashboard_url`, `name`, published_tag, publisher_last_updated_epoch_ms) VALUES  &amp;quot;superset_dashboard://my_cluster.1/3&amp;quot;:(&amp;quot;http://localhost:8088/superset/dashboard/3/&amp;quot;,&amp;quot;my_dashboard&amp;quot;,&amp;quot;unique_tag&amp;quot;,timestamp());
...

# insert a DASHBOARD_WITH_TABLE relationship/edge

INFO:databuilder.publisher.nebula_csv_publisher:Importing data in edge files: [&amp;apos;/tmp/amundsen/dashboard/relationships/Dashboard_Table_DASHBOARD_WITH_TABLE.csv&amp;apos;]
DEBUG:databuilder.publisher.nebula_csv_publisher:Query:
INSERT edge `DASHBOARD_WITH_TABLE` (`END_LABEL`, `START_LABEL`, published_tag, publisher_last_updated_epoch_ms) VALUES &amp;quot;superset_dashboard://my_cluster.1/3&amp;quot;-&amp;gt;&amp;quot;postgresql+psycopg2://my_cluster.warehouse/orders&amp;quot;:(&amp;quot;Table&amp;quot;,&amp;quot;Dashboard&amp;quot;,&amp;quot;unique_tag&amp;quot;, timestamp()), &amp;quot;superset_dashboard://my_cluster.1/3&amp;quot;-&amp;gt;&amp;quot;postgresql+psycopg2://my_cluster.warehouse/customers&amp;quot;:(&amp;quot;Table&amp;quot;,&amp;quot;Dashboard&amp;quot;,&amp;quot;unique_tag&amp;quot;, timestamp());&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#superset-dashboard-34" name="superset-dashboard-34"&gt;&lt;/a&gt;验证 Superset Dashboard 元数据&lt;/h4&gt;  &lt;p&gt;通过在 Amundsen 中搜索它，我们现在可以获得 Dashboard 信息。&lt;/p&gt;  &lt;p&gt;我们也可以从 NebulaGraph Studio 进行验证。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="401" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/20.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;注：可以参阅 Dashboard 抓取指南中的 Amundsen Dashboard 图建模：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="466" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/21.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#superset-35" name="superset-35"&gt;&lt;/a&gt;用 Superset 预览数据&lt;/h3&gt;  &lt;p&gt;Superset 可以用来预览表格数据，文档可以参考   &lt;a href="https://www.amundsen.io/amundsen/frontend/docs/configuration/#preview-client"&gt;https://www.amundsen.io/amundsen/frontend/docs/configuration/#preview-client&lt;/a&gt;，其中   &lt;code&gt;/superset/sql_json/&lt;/code&gt;的 API 被   &lt;code&gt;Amundsen Frontend Service&lt;/code&gt;调用，取得预览信息。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="486" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/22.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-36" name="h-36"&gt;&lt;/a&gt;开启数据血缘信息&lt;/h3&gt;  &lt;p&gt;默认情况下，数据血缘是关闭的，我们可以通过以下方式启用它：&lt;/p&gt;  &lt;p&gt;第一步，   &lt;code&gt;cd&lt;/code&gt;到 Amundsen 代码仓库下，这也是我们运行   &lt;code&gt;docker-compose -f docker-amundsen-nebula.yml up&lt;/code&gt;命令的地方：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;cd amundsen&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;第二步，修改 frontend 下的 TypeScript 配置&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;--- a/frontend/amundsen_application/static/js/config/config-default.ts
+++ b/frontend/amundsen_application/static/js/config/config-default.ts
   tableLineage: {
-    inAppListEnabled: false,
-    inAppPageEnabled: false,
+    inAppListEnabled: true,
+    inAppPageEnabled: true,
     externalEnabled: false,
     iconPath: &amp;apos;PATH_TO_ICON&amp;apos;,
     isBeta: false,&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;第三步，重新构建 Docker 镜像，其中将重建前端图像。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker-compose -f docker-amundsen-nebula.yml build&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;第四步，重新运行   &lt;code&gt;up -d&lt;/code&gt;以确保前端用新的配置：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker-compose -f docker-amundsen-nebula.yml up -d&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;结果大概长这样子：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ docker-compose -f docker-amundsen-nebula.yml up -d
...
Recreating amundsenfrontend           ... done&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;之后，我们可以访问   &lt;code&gt;http://localhost:5000/lineage/table/gold/hive/test_schema/test_table1&lt;/code&gt;看到   &lt;code&gt;Lineage （beta）&lt;/code&gt;血缘按钮已经显示出来了：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="395" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/23.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;我们可以点击   &lt;code&gt;Downstream&lt;/code&gt;查看该表的下游资源：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="395" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/24.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;或者点击血缘按钮查看血缘的图表式：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="395" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/25.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;也有用于血缘查询的 API。&lt;/p&gt;  &lt;p&gt;下面这个例子中，我们用 cURL 调用下这个 API：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker run -it --rm --net container:amundsenfrontend nicolaka/netshoot

curl &amp;quot;http://amundsenmetadata:5002/table/snowflake://dbt_demo.public/raw_inventory_value/lineage?depth=3&amp;amp;direction=both&amp;quot;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上面的 API 调用是查询上游和下游方向的 linage，表   &lt;code&gt;snowflake://dbt_demo.public/raw_inventory_value&lt;/code&gt;的深度为 3。&lt;/p&gt;  &lt;p&gt;结果应该是这样的：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;{
    &amp;quot;depth&amp;quot;: 3,
    &amp;quot;downstream_entities&amp;quot;: [
        {
            &amp;quot;level&amp;quot;: 2,
            &amp;quot;usage&amp;quot;: 0,
            &amp;quot;key&amp;quot;: &amp;quot;snowflake://dbt_demo.public/fact_daily_expenses&amp;quot;,
            &amp;quot;parent&amp;quot;: &amp;quot;snowflake://dbt_demo.public/fact_warehouse_inventory&amp;quot;,
            &amp;quot;badges&amp;quot;: [],
            &amp;quot;source&amp;quot;: &amp;quot;snowflake&amp;quot;
        },
        {
            &amp;quot;level&amp;quot;: 1,
            &amp;quot;usage&amp;quot;: 0,
            &amp;quot;key&amp;quot;: &amp;quot;snowflake://dbt_demo.public/fact_warehouse_inventory&amp;quot;,
            &amp;quot;parent&amp;quot;: &amp;quot;snowflake://dbt_demo.public/raw_inventory_value&amp;quot;,
            &amp;quot;badges&amp;quot;: [],
            &amp;quot;source&amp;quot;: &amp;quot;snowflake&amp;quot;
        }
    ],
    &amp;quot;key&amp;quot;: &amp;quot;snowflake://dbt_demo.public/raw_inventory_value&amp;quot;,
    &amp;quot;direction&amp;quot;: &amp;quot;both&amp;quot;,
    &amp;quot;upstream_entities&amp;quot;: []
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;实际上，这个血缘数据就是在我们的   &lt;a href="https://github.com/amundsen-io/amundsen/blob/main/databuilder/databuilder/extractor/dbt_extractor.py"&gt;dbtExtractor&lt;/a&gt;执行期间提取和加载的，其中   &lt;code&gt;extractor .dbt.{DbtExtractor.EXTRACT_LINEAGE}&lt;/code&gt;默认为   &lt;code&gt;true&lt;/code&gt;，因此，创建了血缘元数据并将其加载到了 Amundsen。&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#nebulagraph-37" name="nebulagraph-37"&gt;&lt;/a&gt;在 NebulaGraph 中洞察血缘&lt;/h4&gt;  &lt;p&gt;使用图数据库作为元数据存储的两个优点是：&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;图查询本身是一个灵活的 DSL for lineage API&lt;/strong&gt;，例如，这个查询帮助我们执行 Amundsen 元数据 API 的等价的查询：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;MATCH p=(t:`Table`) -[:`HAS_UPSTREAM`|:`HAS_DOWNSTREAM` *1..3]-&amp;gt;(x)
WHERE id(t) == &amp;quot;snowflake://dbt_demo.public/raw_inventory_value&amp;quot; RETURN p&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;来，在 NebulaGraph Studio 或者 Explorer 的控制台中查询下：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="401" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/26.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;​渲染下这个结果：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="401" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/27.png" width="690"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;   &lt;a href="https://itindex.net/relian#h-38" name="h-38"&gt;&lt;/a&gt;提取数据血缘&lt;/h4&gt;  &lt;p&gt;这些血缘信息是需要我们明确指定、获取的，获取的方式可以是自己写 Extractor，也可以是用已有的方式。比如：dbt 的 Extractor 和 Open Lineage 项目的 Amundsen Extractor。&lt;/p&gt;  &lt;h5&gt;   &lt;a href="https://itindex.net/relian#dbt-39" name="dbt-39"&gt;&lt;/a&gt;通过 dbt&lt;/h5&gt;  &lt;p&gt;这个在刚才已经展示过了，dbt 的 Extractor 会从表级别获取血缘同其他 dbt 中产生的元数据信息一起被拿到。&lt;/p&gt;  &lt;h5&gt;   &lt;a href="https://itindex.net/relian#open-lineage-40" name="open-lineage-40"&gt;&lt;/a&gt;通过 Open Lineage&lt;/h5&gt;  &lt;p&gt;Amundsen 中的另一个开箱即用的血缘 Extractor 是   &lt;a href="https://www.amundsen.io/amundsen/databuilder/#openlineagetablelineageextractor"&gt;OpenLineageTableLineageExtractor&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://openlineage.io/"&gt;Open Lineage&lt;/a&gt;是一个开放的框架，可以将不同来源的血统数据收集到一个地方，它可以将血统信息输出为 JSON 文件，参见文档   &lt;a href="https://www.amundsen.io/amundsen/databuilder/#openlineagetablelineageextractor"&gt;https://www.amundsen.io/amundsen/databuilder/#openlineagetablelineageextractor&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;下边是它的 Amundsen Databuilder 例子：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;dict_config = {
    # ...
    f&amp;apos;extractor.openlineage_tablelineage.{OpenLineageTableLineageExtractor.CLUSTER_NAME}&amp;apos;: &amp;apos;datalab&amp;apos;,
    f&amp;apos;extractor.openlineage_tablelineage.{OpenLineageTableLineageExtractor.OL_DATASET_NAMESPACE_OVERRIDE}&amp;apos;: &amp;apos;hive_table&amp;apos;,
    f&amp;apos;extractor.openlineage_tablelineage.{OpenLineageTableLineageExtractor.TABLE_LINEAGE_FILE_LOCATION}&amp;apos;: &amp;apos;input_dir/openlineage_nd.json&amp;apos;,
}
...

task = DefaultTask(
    extractor=OpenLineageTableLineageExtractor(),
    loader=FsNebulaCSVLoader())&lt;/code&gt;&lt;/pre&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian#h-41" name="h-41"&gt;&lt;/a&gt;回顾&lt;/h2&gt;  &lt;p&gt;整套元数据治理/发现的方案思路如下：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;将整个数据技术栈中的组件作为元数据源（从任何数据库、数仓，到 dbt、Airflow、Openlineage、Superset 等各级项目）&lt;/li&gt;   &lt;li&gt;使用 Databuilder（作为脚本或 DAG）运行元数据 ETL，以使用 NebulaGraph 和 Elasticsearch 存储和索引&lt;/li&gt;   &lt;li&gt;从前端 UI（使用 Superset 预览）或 API 去使用、消费、管理和发现元数据&lt;/li&gt;   &lt;li&gt;通过查询和 UI 对 NebulaGraph，我们可以获得更多的可能性、灵活性和数据、血缘的洞察&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" height="499" src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/data-lineage/1.svg" width="315"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a href="https://itindex.net/relian#h-42" name="h-42"&gt;&lt;/a&gt;涉及到的开源&lt;/h3&gt;  &lt;p&gt;此参考项目中使用的所有项目都按字典顺序在下面列出。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Amundsen&lt;/li&gt;   &lt;li&gt;Apache Airflow&lt;/li&gt;   &lt;li&gt;Apache Superset&lt;/li&gt;   &lt;li&gt;dbt&lt;/li&gt;   &lt;li&gt;Elasticsearch&lt;/li&gt;   &lt;li&gt;meltano&lt;/li&gt;   &lt;li&gt;NebulaGraph&lt;/li&gt;   &lt;li&gt;Open Lineage&lt;/li&gt;   &lt;li&gt;Singer&lt;/li&gt;&lt;/ul&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;p&gt;   &lt;strong&gt;谢谢你读完本文&lt;/strong&gt;(///▽///)&lt;/p&gt;  &lt;p&gt;如果你对本文感兴趣，现在可以用用 NebulaGraph Cloud 来搭建自己的数据治理系统哟，快来节省大量的部署安装时间来搞定业务吧~ Nebula Graph 阿里云计算巢现 30 天免费使用中，   &lt;a href="http://c.nxw.so/6P6BU"&gt;点击链接&lt;/a&gt;来用用图数据库吧~&lt;/p&gt;  &lt;p&gt;想看源码的小伙伴可以前往 GitHub 阅读、使用、(^з^)-☆ star 它 →   &lt;a href="http://c.nxw.so/8yTlk"&gt;GitHub&lt;/a&gt;；和其他的 NebulaGraph 用户一起交流图数据库技术和应用技能，留下   &lt;a href="http://c.nxw.so/9jvQN"&gt;「你的名片」&lt;/a&gt;一起玩耍呢~&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62561-%E6%95%B0%E6%8D%AE-%E6%B2%BB%E7%90%86-%E5%BC%80%E6%BA%90</guid>
      <pubDate>Wed, 28 Dec 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>文字语义纠错技术探索与实践-张健</title>
      <link>https://itindex.net/detail/62552-%E6%96%87%E5%AD%97-%E8%AF%AD%E4%B9%89-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;h1&gt;  &lt;strong&gt;  背景    &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;文本语义纠错的使用场景非常广泛，基本上只要涉及到写作就有文本纠错的需求。书籍面市前就有独立的校对的环节来保障出版之后不出现明显的问题。在新闻中我们也时不时看到因为文字审核没到位造成大乌龙的情况，包括上市公司在公开文书上把“临时大会”写成为“临死大会”，政府文件把“报效国家”写成了“报销国家”。有关文本纠错的辅助工具能给文字工作人员带来较大的便利，对审核方面的风险也大幅降低。&lt;/p&gt;



 &lt;p&gt;除了不同的写作场景，文本纠错还会用在其他一些智能处理系统中，具体的情况包括：音频通话记录经过自动语音识别（ASR）转写成文本之后，存在一些转译错误；光学字符识别（OCR）系统识别图片中的文字并进行提取，会存在字符识别错误；在搜索引擎或自动问答系统里面，用户在查询过程中的输入错误，往往会导致系统无法理解用户的真实意图，需要进行查询纠正改写。这些情况都需要通过文本纠错技术来进行修正，使产品整体的用户体验更加友好。&lt;/p&gt;



 &lt;p&gt;文本语义纠错在学术领域有三个子任务，分别是拼写检查（Spelling Check）、语法检错（Grammatical Error Detection）和语法纠错（Grammatical Error Correction）。其中语法检错是对文本中的语法错误进行检测，拼写检查是对文本中的错别字进行修正，语法纠错是纠正文本中的语法错误。拼写检查在英文场景表现为单词拼写错误，在中文场景表现为音近形近错别字。而语法纠错除此之外，还包括字词缺失、字词冗余、字词使用不当、语序不当等错误类型。语法纠错区别于拼写检查的一个显著特点是，语法纠错纠正后的文本和原始文本的长度不一定相等，而拼写检查纠正前后的文本长度都是保持一致的，这也决定了两者的算法支持存在差异。一般来说，拼写检查可以看作为语法纠错的一个任务子集。&lt;/p&gt;



 &lt;p&gt;我们对语法纠错的问题作一下形式化定义，输入的原始文本定义为X={x1,x2,...,xn};原始文本正确的纠正结果文本序列定义为Y={y1,y2,...,ym}，算法预测输出的文本，定义为P={p1,p2,...,pk}。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;评估指标 &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;在开始我们的文本语义纠错算法探索之旅之前，我们先思考一个问题，究竟怎么样的模型表现才是公认更有效的，这个好坏应该从何种方式、如何量化地评估出来。这也是我们在解决其他所有类型的NLP任务都需要先考虑的问题，这个问题就是如何定义我们的评测指标。下面罗列了纠错算法常用的一些评测指标：&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;M2（MaxMatch）&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;M2指标主要是通过计算输出文本和原始文本之间的编辑集合G，然后与人工标注的编辑集合E结合，计算准确率、召回率、F0.5值（采用F0.5表示对准确率更加关注）。这里的编辑理解为一个转换动作，经过一组转换动作，可以完成原始文本到纠正文本的转换，M2指标定义形如：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/2f96b650-a98a-4bf3-bf58-fdc5258275b8.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;F0.5=1.25*RP/(R+0.25P)&lt;/p&gt;



 &lt;p&gt;下表罗列了一组示例和计算过程：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/81f46c04-6037-4075-b7aa-0d62628ab074.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;表 1 纠错文本示例&lt;/p&gt;



 &lt;p&gt;其中编辑集合G={孜→自，书→书写}，人工标注编辑集合E={孜→自,俱→具,读书→读}&lt;/p&gt;



 &lt;p&gt;可以计算出来:&lt;/p&gt;



 &lt;p&gt;P=1/2=0.5&lt;/p&gt;



 &lt;p&gt;R=1/3=0.33&lt;/p&gt;



 &lt;p&gt;F0.5=1.25*0.33*0.5/(0.33+0.25*0.5)=0.45&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02 &lt;/strong&gt;  &lt;strong&gt;ERRANT&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;ERRANT[1]是升级版的M2。M2的局限性也比较明显，依靠前置的人工标注，有比较大的工作量，而且人工标注编辑集合产生的方式可能不太一致，导致匹配不准。ERRANT在生成标准答案的编辑集合和生成预测的编辑集合都采用了自动判别的方式，同时支持了25种的错误类型，输出了更丰富维度的错误报告信息。缺点是该工具面向英文，中文需要做较大改造。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;面向标注形态的其他指标&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;上述两者在处理纠错任务评测时存在一些缺点，包括M2不支持检错性能评估，编辑不能正确反映合理纠错动作等等。  &lt;br /&gt;  &lt;br /&gt;我们会在一些学术评测上看到，根据对待纠文本进行的错误标注类型来制定的评测指标。下面举了NLPCC2022语法纠错评测指标为例，它对应的错误类型总共有赘余(Redundant Words，R)、遗漏(Missing Words，M)、误用(Word Selection，S)、错序(Word Ordering Errors，W)四类，评估的维度包含以下方面：&lt;/p&gt;



 &lt;ul&gt;
  &lt;li&gt;假阳性（False Positive）：正确句子被判包含错误的比例。&lt;/li&gt;



  &lt;li&gt;侦测层（Detective-level）：对句子是否包含错误做二分判断。从句子是否有错，判断p/r/f1&lt;/li&gt;



  &lt;li&gt;识别层（Identification-level）：给出错误点的错误类型。按一个句子的错误种类计算p/r/f1&lt;/li&gt;



  &lt;li&gt;定位层（Position-level）：对错误点的位置和覆盖范围进行判断，以字符偏移量计。错误位置是否对计算p/r/f1&lt;/li&gt;



  &lt;li&gt;修正层（Correction-level）：提交针对字符串误用（S）和缺失（M）两种错误类型的修正词语。修正词语可以是一个词，也可以是一个词组。M/S的修正词语角度&lt;/li&gt;
&lt;/ul&gt;



 &lt;p&gt;由于纠错任务本身的特殊性（同一个错误的文本可以有多种正确的纠正答案，或者同一个位置可以采用不同的错误类型进行标注），目前现存的评测指标大都有其局限性，如何定义主客观、统一、合理的语法纠错评测指标仍然在不断探讨。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt; 公开数据集&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;在确定了评估指标之后，我们已经确定了评判算法好坏的一个标准。锅已经端好，就等米下锅了,数据对于算法研发人员来说是必需品，一方面它是验证效果的信息来源,另一方面它是进行模型构建的训练语料。比较好的方式是从公开的渠道获取比较优质的标注数据。目前公开的中文语义纠错数据集包括NLPCC2018[2]、NLPTEA2020[3]、SIGHAN2015[4]等,较多是非母语学生学习汉语收集得来的语料集，训练和验证的数据标注形式如图所示:&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/ccee8a06-344d-443d-baaf-12959d28383b.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图1 公开数据集（NLPTEA2020、NLPCC2020和SIGHAN2015）&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;无监督方法&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;文本语义纠错的算法整体可以分成无监督和有监督的两种方式，我们先从无监督的方法开始看。无监督方法的核心是如何构建一个好用的语言模型，并且用在纠错的任务上。对于NLPer来说，我们经历了太多的预训练语言模型，像BERT、XLNet、GPT3等等，其本质还是语言模型或者说经典语言模型的一些变种。语言模型实际上是对文本序列的概率分布进行建模，通俗地来表达，语言模型是判断一句话是不是符合常理，或者说话应该怎么说才合理（符合概率分布）。这个正好就对应上了纠错任务的本质需求，我们从最经典的N元语言模型开始来介绍一下语法纠错的处理逻辑。   &lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;n元语言模型&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;一个语言模型构建字符串的概率分布p(W)，假设p(W)是字符串作为句子的概率，则概率由下边的公式计算：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/68f2b5ca-b777-4865-8668-29f0032daf32.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;但是这样去计算句子概率会导致庞大的计算量，导致根据马尔科夫假设，一个词只和他前面n-1个词相关性最高，这就是n元语法模型，简化后的计算公式为：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/1ea20eca-647d-435c-a787-719f694e93a4.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;在得到这个结论之后，我们尝试使用N元语言模型来解决拼写检查的问题。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;假设我们采用的是5元语言模型，训练阶段使用大量的语料来进行统计所有的p(w5|w1w2w3w4)并存储起来。在预测阶段，设定待纠正的文本序列为W={w1,w2,...,wn}，针对每个位置的wk，我们通过预先构建好的混淆集获得w的音近形近字wk&amp;apos;。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;然后通过上述公式分别计算原始文本和修改文本的句子概率P(w1...wk...wn)、P(w1...wk&amp;apos;...wn)。如果P(w1...wk’...wn)&amp;gt;P(w1...wk...wn)，则说明修改后文本的通顺度提升（概率升高），可以接受该纠正修改（wk替换为wk&amp;apos;）。&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;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/2a13b0c9-02e2-4b46-bbca-739442d93356.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图2 N元语言模型纠错执行计算过程&lt;/p&gt;



 &lt;p&gt;上述过程比较好理解，同时可以明显看出来一些硬伤，包括会OOV（未登录词）问题导致语言模型计算出来的概率为0；模型会过分优待高频短串，或者忽视低频短串。这时候需要通过平滑技术来改善概率算法，典型平滑方法包含Add-one、Interpolation和Modified Kneser-ney等。此外，仍有些难以通过技术手段解决的问题，包括上下文范围局限较大（n 的增加会导致计算和资源消耗成倍增加）和缺少泛化（缺乏实际予以的理解），此时需要引入基于神经网络的语言模型。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;基于神经网络的语言模型&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;比较经典的基于神经网络的语言模型，数学表达式可以写为：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/832df924-e20f-4732-b0e7-854f6b37fbe5.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;以k元文法为例，把前k-1个词作为特征，用softmax预测最后一个词。&lt;/p&gt;



 &lt;p&gt;一般基于神经网络的语言模型设计得更加复杂，会把上下文的信息形成特征，来预测当中的每一个词。定义基于上下文context下wi的预测概率为P(wi|context_i),句子的概率可以表示为：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/87ec0acb-8593-4e18-88d4-28ebb4b57258.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;方法[5]就是采用了BERT和GPT作为基础的语言模型来计算句子的概率。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;有监督方法&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;无监督的纠错算法在处理文本时存在以下弱点：容易受局部高频或低频的序列影响，效果不够稳定；在需要对准确率和召回率进行平衡调整时，不太好通过阈值的方式进行控制；可以较好应用在拼写检查的任务上，但是对于句子长度有变化的语法纠错任务支持就比较弱。此时需要使用有监督算法来作为实现手段。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;NMT/Seq2Seq&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;解决字词冗余/缺失这类纠错前后句子长度有变化的任务，我们第一感觉就想起可以通过文本生成的方式来训练对应的模型实现该功能。而且语法纠错任务和文本生成任务的形态基本上是一致的，也导致了文本生成模型很自然地被研究者注意，引入到语法纠错的任务领域。&lt;/p&gt;



 &lt;p&gt;NMT-based GEC[6]是第一篇通过使用神经网络机器翻译来实现语法纠错的文章。2014年seq2seq模型一提出即引发了较大反响，后续seq2seq成为了文本生成的主流结构。seq2seq将一个作为输入的序列映射为一个作为输出的序列，这一过程由编码（Encoder）输入与解码（Decoder）输出两个环节组成, 前者负责把序列编码成一个固定长度的向量，这个向量作为输入传给后者，输出可变长度的向量。下图展现了一个基础的seq2seq结构。&lt;/p&gt;


 &lt;div&gt;
  &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/326e96bc-a4f8-4c11-84c6-6e52e6de0f6a.jpg"&gt;&lt;/img&gt;&lt;/div&gt;


 &lt;p&gt;图3  seq2seq结构&lt;/p&gt;



 &lt;p&gt;方法[7]使用了经典的Encoder-Decoder模型结构来解决中文语法纠错问题，嵌入层使用了特殊的嵌入表示，同时在编码层使用了卷积神经网络强化了纠错的局部性，具体的模型结构如下：&lt;/p&gt;


 &lt;div&gt;
  &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/93b9891a-35da-4951-8c17-72af28cd587a.jpg"&gt;&lt;/img&gt;&lt;/div&gt;


 &lt;p&gt;图4 Encoder-Decoder结构纠错模型&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02 &lt;/strong&gt;  &lt;strong&gt;LaserTagger&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;由于书写错误的出现概率普遍不高，纠错任务本身的输入输出存在大量重叠（基本不用改），所以大多数文本可以保持不变。但是我们在通过seq2seq的方式进行实现时，对于正常的字符也要全部进行预测，造成效率非常低下。因此谷歌在EMNLP 2019提出了LaserTagger，在使用Encoder-Decoder的模型结构条件下，把预测的内容从文字变成了编辑操作类型。lasertagger其模型结构（采用BERT作为编码层、自回归Transformer作为解码层）如下所示：  &lt;br /&gt;  &lt;br /&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/86e82b87-6d4c-4671-b7a3-c5e9c3a67112.jpg"&gt;&lt;/img&gt;&lt;/p&gt;



 &lt;p&gt;图5 LaserTagger纠错模型&lt;/p&gt;



 &lt;p&gt;编辑操作类型包含Keep（将单词复制到输出中），Delete（删除单词）和Add（在之前添加短语X），其中被添加的短语来自一个受限的词汇表。  &lt;br /&gt;通过结构的改造，lasertagger体现了推理速度快和样本训练效率高的有点。因为预测的类型只有三种，相对于seq2seq而言，解码的空间大幅降低，推理性能提升明显，相对于BERT+seq2seq的模型结构，larserTagger的性能提升接近100倍。同时因为预测的内容求解空间也大幅降低，所以对样本的需求量也大幅减少，在1000份的样本下也能取得不错的效果。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03 &lt;/strong&gt;  &lt;strong&gt;PIE&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;与LaserTagger同年提出来的PIE（Parallel Iterative Edit Models）[8]同样是针对seq2seq 生成文本的可控性较差，推理速度也比较慢的问题进行来改进。与LarserTagger类似，PIE构造模型来对编辑操作进行预测，不过编辑操作的类型稍有区别，多了一个替换（replace)和词性变换（面向英文）。在处理替换和添加操作时，PIE将BERT编码层进行了扩展来支持替换和添加的信息输入，采用了一个双层的双向transformer，结构如下所示：  &lt;br /&gt;  &lt;br /&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/ce4efddc-017c-431b-8cca-212469d098b9.jpg"&gt;&lt;/img&gt;&lt;/p&gt;



 &lt;p&gt;图6 PIE纠错模型&lt;/p&gt;



 &lt;p&gt;上图表示了一个长度为3的文本输入（x1,x2,x3)。在最底层的输入层，M表示mask标识符的嵌入向量，p表示位置嵌入，x表示词嵌入。在中间层和输出层，r表示对应位置的替换信息，h表示对应位置的的原始信息，a表示对应位置的插入信息。之后利用三类信息来分别计算不同操作的概率，并归一化，CARDT 分别代表复制、插入、替换、删除、词形变换，计算公式如下：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/06b728d3-f079-45ff-97bf-9c5defebc4b9.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;纠错过程中，PIE模型输出概率最高的编辑操作，完成修改后再迭代地进行预测，直至句子不发生改变后停止。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;PIE定义的结构可以实现在并行解码的同时保持较高的准确率，它在这篇文章第一次提出了seq2edit的概念。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04 &lt;/strong&gt;  &lt;strong&gt;GECToR&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;GECToR[9]提出了一种序列标注模型，编码层由预训练的 BERT 型 transformer 组成，上面堆叠两个线性层，顶部有 softmax 层。模型输出的标签包含了基本变换和g-变换两种类型。其中基本变换包含保留（KEEP）、删除（DELETE）、添加（APPEND）和替换（REPLACE)。g-变换主要面向英文，针对了英语的语法变化总结出了5大类（大小写、单词合并、单词拆分、单复数和时态）29个小类的状态变换。  &lt;br /&gt;  &lt;br /&gt;GECToR另外两个亮点是引入了不同的预训练Transformer解码器（包括XLNet、RoBERTa、ALBERT、BERT和GPT-2）并进行了比较，以及采用了三阶段的训练方式。第一阶段使用了大量（九百万）实验合成的包含语法错误+语法正确的句子对进行预训练，第二阶段使用了少量的公开纠错数据集的句子对进行Fine-tuning，第三阶段使用了语法错误+正确和语法正确+正确的句子对来进行Fine-tuning，实验证明第三阶段的Fine-tuning有效果提升。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;在预测阶段，GECToR也是采用了多轮预测的方案。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05 &lt;/strong&gt;  &lt;strong&gt;PLOME&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;PLOME[10]在2021ACL发表，是针对中文文本纠错任务构建的预训练语言模型，结构和BERT比较类似（12层Transformer）。PLOME的创新点主要在于采用了基于混淆集的掩码策略、把拼音和字形信息作为模型输入以及把字符和拼音的预测任务作为了模型的训练和微调目标。  &lt;br /&gt;  &lt;br /&gt;PLOME的掩码策略主要是基于以下4种：字音混淆词替换(Phonic Masking)、字形混淆词替换(Shape Masking)、随机替换（Random Masking）、原词不变（Unchanging）。PLOME的掩码策略同样仅遮盖15%的token，且4种MASK策略占比分别为: 60% 、15%、10%、15%。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;词嵌入模块方面，PLOME采用了字符嵌入(character embedding)、位置嵌入( position embedding)、语音嵌入(phonic embedding)和形状嵌入(shape embedding)。其中，字符嵌入和位置嵌入与BERT的输入一致。其中构建语音嵌入时，使用Unihan数据库得到字符-拼音的映射表(不考虑音调)， 然后将每一个字的多个拼音字母序列输入到GRU网络中，得到该字的拼音嵌入向量。同样，构建字形嵌入时，使用Chaizi数据库得到字形的笔画顺序，然后将字形的笔画顺序序列输入到GRU网络中，得到该字的字形嵌入向量。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;在训练任务方面，PLOME训练了2个任务，字符预测和BERT一样，增加了拼音的预测，预测被替换词的正确发音，更够更好解决同音和音近错误。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;PLOME预训练语言模型的下游任务主要是文本纠错任务。该任务的输入是字符序列 ,输出是预测的字符序列。该论文仅在拼写检查任务上做了验证。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;06&lt;/strong&gt;  &lt;strong&gt; 其他策略&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;（1）COPY机制&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;COPY机制同样是利用目标文本和源文本有大量重复这个特点。研究[11]提出了Copy-Augment的GEC模型，其主要思想是：在生成序列过程中，考虑两个生成分布：分别是复制输入序列中的词语（0/1表示是否复制)的概率分布和从候选词典中的词语生成的概率分布。将两者的概率分布加权求和作为最终生成的概率分布，进而预测每一个时刻生成的词语。基本架构如下：  &lt;br /&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221221/9169ccac-2eb0-47b6-b540-3754a8ce576d.jpg"&gt;&lt;/img&gt;&lt;/p&gt;



 &lt;p&gt;图7 COPY机制&lt;/p&gt;



 &lt;p&gt;该模型将简单的词语复制任务交给了Copy机制，将模型结构中的Attention等结构更多地用来学习比较难的新词生成，对训练更加可控。&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;br /&gt;基于规则这个比较简单，我们可以按照错误的类型（字词冗余、缺失、词序错误等）针对性地制定策略构造包含语法错误的样本，然后扔到模型来进行训练。不过基于规则的方式有点过于粗暴，很可能规则生成的错误与实际产生的错误差距比较大，或者比较不符合常规认知。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;第二种方式就是基于生成。最早基于生成的数据增强方式应该是回译法，就是将文本从一个语种翻译到另外一个语种，然后再翻译回来，从而构造了句子对，这种数据增强形式针对正常的编辑写作可能不太有效，更加符合跨语言学习的用户的错误特点。另外有研究[12]结合了分类器和自编码器来联合训练，达到生成固定类型错误样本的目的。而研究[13]通过对GEC模型进行对抗攻击，可以生成有价值的带有语法错误的句子，可以利用生成的句子训练GEC模型，提升性能的同时提升鲁棒性。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;处理难点与技术挑战 &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;语料收集&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;目前公开的中文语义纠错数据集主要是不同母语的人学习汉语作为第二语言收集得来的语料集，目前大部分关于语法纠错的算法模型都是基于这些数据集来做效果验证的，不过我们实际中要处理的数据通常并不是同样的形式诞生，更多是掌握汉语作为母语的人由于失误导致的语法错误，这种情况和公开预料的情况差别比较大，错误的分布差距也比较大，从而通过公开语料集训练得来的模型在上线到正常的业务流程里面，效果通常都会比较一般。    &lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;长依赖&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;长距离包括跨语句依赖在论文等文本中很常见，一旦出现错误，很难察觉并纠正。当前语法研究大多集中在单个语句的语法检查和纠错，很少涉及长距离语法问题，相关数据集和模型方法缺失，是语法纠错的难题之一。  &lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;模型的泛化能力与鲁棒性&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;一般来说，不同行业、不同领域的文本在措辞运用、表达习惯和专有名词等方面都存在较大的差异。譬如说政务机关红头文件非常严谨的语言表达和自媒体新闻相对较自由的文风就有明显的差别，又譬如金融行研报告和医学论文在基本内容和专业术语上也截然不同。在一个领域性能出色的纠错模型在切换到另外一个领域，往往效果下降明显。如何提升模型的泛化能力和鲁棒性，面临着巨大的技术挑战。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;效果指标与体验的平衡&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;SOTA的指标可以刷到，但是这些模型一旦介入实际场景的数据，效果会差得一塌糊涂，这个一方面是由于模型和场景紧密相关，另外一方面是，通常公开数据集的错误分布是呈高密度，但是实际场景是低密度，会容易导致非常高的误判。譬如说SOTA里面准确率的指标是80%，对于在低密度错误的样本中，很可能准确率会下降到20~30%左右。纠错系统的体验会比较差。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05&lt;/strong&gt;  &lt;strong&gt;效果指标与纠错性能的平衡&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;工业界往往会采用pipeline的方式，先对文本进行检错，如果检测出来有错误，再对文本进行纠错处理。但是这个检错阶段的错误会传递到纠错阶段，导致效果下降。如果直接走seq2seq或seq2edit的纠错模型，或者需要融合多种模型策略来生成最终纠错结果，纠错的性能会下降非常快，部分实验3000字的纠错可能需要长达40~60秒，这个无法处理大量并发的文本纠错需求。我们需要再效果和性能上取得平衡，或者有更好的方法在保障效果指标的前提下提升纠错性能。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;达观在语义纠错方面的产品实践 &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;达观数据在语义纠错方面有比较深入的产品实践，开发出的投行质控系统和公文智能处理系统均处理了相关场景。&lt;/p&gt;



 &lt;p&gt;达观投行质控系统基于深度学习、NLP（自然语言处理）算法，帮助用户解决几大文书审核场景，包括:文书格式纠错，文字纠错和完整性审核；文档目录智能识别，一键定位：文档条款内容智能提取，方便业务人员对条款内容进行预审；支持文档多版本的内容比对等。&lt;/p&gt;



 &lt;p&gt;达观智能公文处理系统，严格遵循《党政机关公文处理工作条例》和《党政机关公文格式》规定，通过公文智能分析、公文知识库引用、公文审校、公文排版、公文格式纠错、公文内容语义纠错、公文在线比对修改等一体化的功能，实现基础字词校对准确率超90%、法律引用校验和公务文书完整性校对准确率超95%，有力提升政府机关整体公文质量，避免公文“带病”发布情况，确保政府机关公信度。&lt;/p&gt;



 &lt;p&gt;参考文献：&lt;/p&gt;



 &lt;p&gt;[1] Korre K, Pavlopoulos J. Errant: Assessing and improving grammatical error type classification[C]//Proceedings of the The 4th Joint SIGHUM Workshop on Computational Linguistics for Cultural Heritage, Social Sciences, Humanities and Literature. 2020: 85-89.&lt;/p&gt;



 &lt;p&gt;[2] Zhao Y, Jiang N, Sun W, et al. Overview of the nlpcc 2018 shared task: Grammatical error correction[C]//CCF International Conference on Natural Language Processing and Chinese Computing. Springer, Cham, 2018: 439-445.&lt;/p&gt;



 &lt;p&gt;[3] Rao G, Yang E, Zhang B. Overview of NLPTEA-2020 shared task for Chinese grammatical error diagnosis[C]//Proceedings of the 6th Workshop on Natural Language Processing Techniques for Educational Applications. 2020: 25-35.&lt;/p&gt;



 &lt;p&gt;[4] Tseng Y H, Lee L H, Chang L P, et al. Introduction to SIGHAN 2015 bake-off for Chinese spelling check[C]//Proceedings of the Eighth SIGHAN Workshop on Chinese Language Processing. 2015: 32-37.&lt;/p&gt;



 &lt;p&gt;[5] Alikaniotis D, Raheja V. The unreasonable effectiveness of transformer language models in grammatical error correction[J]. arXiv preprint arXiv:1906.01733, 2019.&lt;/p&gt;



 &lt;p&gt;[6] Yuan Z, Briscoe T. Grammatical error correction using neural machine translation[C]//Proceedings of the 2016 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies. 2016: 380-386.&lt;/p&gt;



 &lt;p&gt;[7] Ren H, Yang L, Xun E. A sequence to sequence learning for Chinese grammatical error correction[C]//CCF International Conference on Natural Language Processing and Chinese Computing. Springer, Cham, 2018: 401-410.&lt;/p&gt;



 &lt;p&gt;[8] Awasthi A, Sarawagi S, Goyal R, et al. Parallel iterative edit models for local sequence transduction[J]. arXiv preprint arXiv:1910.02893, 2019.&lt;/p&gt;



 &lt;p&gt;[9] Omelianchuk K, Atrasevych V, Chernodub A, et al. GECToR--grammatical error correction: tag, not rewrite[J]. arXiv preprint arXiv:2005.12592, 2020.&lt;/p&gt;



 &lt;p&gt;[10] Liu S, Yang T, Yue T, et al. PLOME: Pre-training with misspelled knowledge for Chinese spelling correction[C]//Proceedings of the 59th Annual Meeting of the Association for Computational Linguistics and the 11th International Joint Conference on Natural Language Processing (Volume 1: Long Papers). 2021: 2991-3000.&lt;/p&gt;



 &lt;p&gt;[11] Zhao W, Wang L, Shen K, et al. Improving grammatical error correction via pre-training a copy-augmented architecture with unlabeled data[J]. arXiv preprint arXiv:1903.00138, 2019.&lt;/p&gt;



 &lt;p&gt;[12] Wan Z, Wan X, Wang W. Improving grammatical error correction with data augmentation by editing latent representation[C]//Proceedings of the 28th International Conference on Computational Linguistics. 2020: 2202-2212.&lt;/p&gt;



 &lt;p&gt;[13] Wang L, Zheng X. Improving grammatical error correction models with purpose-built adversarial examples[C]//Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing (EMNLP). 2020: 2858-2869.&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;作者简介&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;张健，达观数据联合创始人，复旦大学计算机软件与理论硕士，曾就职于盛大集团和腾讯文学，担任人工智能和大数据技术专家职位。目前担任达观数据文本应用部总负责人，对于机器学习算法和自然语言处理领域的研发有丰富的实践经验和技术积累，负责客户意见洞察系统、智能客服工单分析系统、文本语义纠错系统、事件分析平台、文本智能审核系统等多个文本应用产品的开发和落地。荣获上海市浦东新区科学技奖、“2021上海科技青年35人引领计划”、上海市青年科技启明星等多个奖项。&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>MIT自然语言处理 NLP开源工具 中文信息处理 人工智能 机器学习</category>
      <guid isPermaLink="true">https://itindex.net/detail/62552-%E6%96%87%E5%AD%97-%E8%AF%AD%E4%B9%89-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Wed, 21 Dec 2022 15:10:41 CST</pubDate>
    </item>
    <item>
      <title>OCR技术发展综述与达观数据的实践经验</title>
      <link>https://itindex.net/detail/62449-ocr-%E6%8A%80%E6%9C%AF-%E5%8F%91%E5%B1%95</link>
      <description>&lt;p&gt;  &lt;strong&gt;光学字符识别OCR技术&lt;/strong&gt;（Optical Character Recognition）是指从图像中自动提取文字信息的技术。这项技术横跨了人工智能里的两大领域：CV（计算机视觉）和NLP（自然语言处理），综合使用了这两大领域中的很多技术成果。&lt;/p&gt;
 &lt;p&gt;在过往40余年的技术发展历程中，OCR始终具备很强的产业应用背景，是计算机领域里少数几个一开始就由工业界和学术界双轮驱动的领域。近年来OCR技术已经在工业界成熟落地应用，学术界里对此的研究热度反而弱于其他方向。甚至有人认为OCR技术已经充分成熟，没有更多研究必要了。然而随着近年来智能文本处理IDP（Intelligent Document Processing）在工业界的逐步落地应用，OCR和IDP相结合的应用场景越来越多，用语义理解NLP的角度进一步去延伸OCR的应用，出现了很多更有产业应用价值的场景。本文回顾了OCR技术的发展历程，并结合达观数据在工程实践方面的经验，介绍与语义分析技术结合后，当前OCR技术的一些最新发展和落地经验。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;  OCR技术的发展历程   &lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;OCR技术的诞生其实比计算机的历史还要悠久，早在1930年代，德国发明家Tausheck（陶舍克）和美国工程师Handel（汉德）分别申请了最早的OCR专利，这比计算机的诞生还要早20年时间。因为当年还完全没有计算机以及相关外设（如今天广泛使用的扫描仪或高拍仪），所以最早提出的OCR技术采用的是机械掩模和模板匹配的方法来处理打字机输出的文档。当时的技术雏形离实际应用还比较遥远。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="1024" src="https://www.52nlp.cn/wp-content/uploads/2022/10/lQDPJxbFXg4Kp6XNCGfNA-iwAMxLLHlQsgUDRG7cQcCqAA_1000_2151-476x1024.jpg" width="476"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;OCR技术真正开始进入办公应用是到了1960年代，引领这项技术的是美国IBM公司。随着二战后美国经济的腾飞，计算机开始进入企业办公领域，在一些日常处理量大且步骤繁琐的场景下OCR开始发挥应用价值。例如1965年纽约世博会展出的IBM1287机器就能自动识别英文字母和数字，且准确率很高。此后逐步被用于一些订单编号的识别派发，和信封邮政编码的识别和邮件分拣等任务中。&lt;/p&gt;
 &lt;p&gt;1980年代起，随着日本经济尤其是电子技术的飞跃，富士通、日立、东芝、NEC等日本科技公司纷纷入场。这个时期的研究特点是图像采集的电子器件得到快速发展，光栅扫描、成像、电子化图像传输等信号采集技术成长迅速，相应的轮廓提取、结构分析等软件算法也开始出现。&lt;/p&gt;
 &lt;p&gt;在以精密电器制造见长的日本企业推动下，扫描仪等采集设备的效果、速度、成本有了长足的进步，对标准打印字符的识别效果越来越好，OCR系统开始普及应用。和其他很多高科技领域里“先从大学有了早期理论研究突破，然后逐步在产业界孵化出实用系统”的方式不同，在这个阶段，OCR技术始终是由工业界主导并取得了良好的应用效果。唯一的例外是对手写字符的识别。因为手写字符的变化太大，各种连笔、涂改、变形等让计算机辨认确实太难（甚至过于潦草的情况下让人辨识都很难），所以作为OCR领域的研究分支，成为了学术界的一个研究热点。尤其1990年代模式识别（Pattern Recognition）兴起，激发了学术研究界对手写字符识别的热情。此时出现大名鼎鼎的MNIST数据集，由美国国家标准与技术研究所（NIST，National Institute of Standards and Technology）发起整理了来自250个不同人的手写数字图片。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/387c683a-bba7-4846-8e74-aca1fe1c042b.jpg"&gt;&lt;/img&gt;  MNIST是OCR乃至模式分类领域最知名的入门数据集&lt;/p&gt;
 &lt;p&gt;此后大量的模式分类以及图像处理论文都以MNIST作为基础，进行各类特征抽取和模式分类的算法研究。至今一些大学人工智能的入门课程还会用MNIST数据集来跑试验，可谓经久不衰。&lt;/p&gt;
 &lt;p&gt;为什么只有手写字符集，不搞打印字符测试数据集？因为对打印字符的识别准确率已经极高（99.9%以上），没有科研必要了……&lt;/p&gt;
 &lt;p&gt;此处顺便一提，百度创始人李彦宏1996年在美国IDD公司工作时也参与了OCR技术研究，其中一篇优秀的研究成果发表在机器学习界知名学术期刊IEEE Transaction PAMI上。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/7b78f50f-06da-45e6-b18a-3acd8d527de3.jpg"&gt;&lt;/img&gt;百度李彦宏的OCR论文，发表于IEEE Trans on PAMI&lt;/p&gt;
 &lt;p&gt;21世纪后，OCR被进一步应用于各行各业里  &lt;strong&gt;卡证票据的识别&lt;/strong&gt;。针对的是日常生活中频繁使用到的发票、身份证、银行卡、营业执照、房产证、驾驶证、汽车牌照等实体证件。在这个阶段，图像扫描技术已经高度成熟了，所以技术研究基本集中在软件算法方面，并细分为信息检测（Detection）和识别（Recognition）两个技术分支分别发展，本文后面有更详细的技术介绍。&lt;/p&gt;
 &lt;p&gt;因为大部分常见的卡证票据都有相对固定的格式布局，所以通常只需要有足够多的训练样本，通过样本标注生成元素的模板定位，就能解决绝大部分问题，不用大费周折使用“智能化”的检测算法。通俗地说，这类应用场景是”数据为王”。&lt;/p&gt;
 &lt;p&gt;自2015年之后OCR技术和应用又迎来了巨大的变化，这次主要来自两个因素的推动。第一个因素是移动手机拍照的普及。在此之前，OCR的图像通常来自于扫描仪、高拍仪等企业级专用采集设备，图像的质量非常高，但因为固定在办公桌面使用，不够方便灵活，限制了应用场景，所以此前的OCR集中于企业级商用。而智能手机的迅速普及，让我们每个人都有了一个“拍摄+上传”的一体化终端，为OCR的应用普及带来了新的历史机遇，随之而来产生了很多新的应用场景。例如各种个人证照、文件等的自助式拍摄和上传，用于远程申报和审批等事项，或者拍摄并识别文件进行内容自动处理等创新场景（例如教育领域的拍题搜答案等）。&lt;/p&gt;
 &lt;p&gt;这个因素也随之带来了一些新的技术问题，例如手机因为拍摄相对随意，个人拍摄的水平参差不齐，会导致图像存在阴影遮挡、角度畸变、失焦模糊等等一堆新的问题。也相应产生了一批解决这些问题的工程手段。（达观数据陈运文）&lt;/p&gt;
 &lt;p&gt;另一个重要因素来自深度学习技术的巨大理论突破。在深度神经网络的旋风刮到OCR领域之前，用于检测和识别的技术可谓是百花齐放，例如各种各样的信号处理（例如Fourier、Radon、Hough、Zernike）特征提炼方法、图像结构的方法（交叉线、圆圈、横竖线条）、各种算子（如SIFT、SURF、各类卷积算子等）等、以及各种映射技术等。针对一些专用的字符类型和特殊应用场景，还有专门构造的人工特征提取技术。&lt;/p&gt;
 &lt;p&gt;但深度学习时代里通过多层网络结构来自动进行特征学习，颠覆了这些传统的人工特征提取过程，效果也有明显的改善。近年来学术界的论文已经是神经网络一统江湖了。加上之前限制深度学习的算力成本大幅度降低，新的更复杂的网络结构层出不穷，将OCR技术的效果不断推上新的台阶。&lt;/p&gt;
 &lt;p&gt;在“智能手机+深度学习”这两个因素共同助推下，近年来OCR技术的研发迎来了  &lt;strong&gt;三个新的热点方向&lt;/strong&gt;，分别是：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;OCR与智能文本处理（IDP）相结合，进行无固定格式文档的语义理解和结构化解析，不仅识别文字本身，而且理解文字的版面、结构、表格元素、段落内容等，从而完成对文本要素信息的还原和结构化抽取工作，并用于智能文档审阅处理等场景&lt;/li&gt;
  &lt;li&gt;OCR与专业领域的符号识别相结合，如数学公式符号、物理公式、化学分子结构图、建筑图纸等等，实现专业领域的应用，如拍图搜题，图纸审核等场景&lt;/li&gt;
  &lt;li&gt;OCR与开放场景的文字识别相结合（常称为STR，Scene Text Recognition），例如路牌、店面招牌、商标文字、户外广告识别等，用于交通、户外消费、自动驾驶等场景&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;这三类当前的热点应用，都有各自的技术难点，也分别衍生出了相应的产品技术解决方案。其中OCR与IDP的结合是目前达观数据的主要研究和应用方向，接下来会进行一些技术分享。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;  OCR技术的发展情况   &lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;当前学术界普遍将OCR处理分为   &lt;strong&gt;图像预处理、文本检测（Detection）、文本识别（Recognition）&lt;/strong&gt;这三大步骤，或者也有将检测和识别合并，直接用  &lt;strong&gt;端到端学习（End-to-End）&lt;/strong&gt;进行处理工作。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;图像预处理&lt;/strong&gt;用于对待处理的原始图像进行一些矫正操作，以助于降低后续的检测和识别难度。例如使用一些工程化技术来调整图像对比度、旋转对齐、进行局部裁剪、折痕和墨点等干扰信息的淡化等都属于相对基础的预处理操作。因为在使用高拍仪或扫描仪等专业采集设备时，图像品质普遍较好，所以在2010年以前对图像预处理方面的系统性的研究并不多，更多集中于对局部的畸变进行校正（Image Rectifier）或图像去噪。&lt;/p&gt;
 &lt;p&gt;经典的图像预处理经常用到各类滤波器（如高斯滤波、BM3D等）进行去噪，另外一些信号处理手段也常用于对图像进行旋转对齐，横平竖直的文档会让后续的检测和识别变得容易得多。&lt;/p&gt;
 &lt;p&gt;智能手机拍摄普及后，光照不均匀、阴影遮挡、局部扭曲、甚至对焦模糊等复杂情况层出不穷，所以在实际工程应用中，图像预处理的好坏对后续识别精度价值很大，尽管这个环节作为OCR的一个非核心环节，受到学术圈的重视较少，各大学术会议上也几乎罕见这方面的研究论文（近年有几篇不错的Text deblurring论文）。但学术和工程的目标是不一样的，工程应用要在琐碎中见真章，会配置很多业务规则和处理步骤。&lt;/p&gt;
 &lt;p&gt;顺便一提，为了增加标注样本的数量，提升算法在不同场景下的鲁棒性，生成对抗网络（GAN）的思想在OCR的样本生成方面也很常用。尤其在标注样本不充足的情况下，用GAN网络结合人工标注和积累的真实样本，日拱一卒不断扩大训练样本库，也是常用的做法。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;文本检测&lt;/strong&gt;是OCR的最重要环节之一（另一个是文本识别），传统的文本检测使用了各种人工构造的特征，例如常见的二值投影、旋转仿射变换、各类图像算子如HoG算子、SURF算子，DPM模型（Deformable Parts Model）等来定位文本行列位置。在2010年前最常见的技术手段为滑动窗口检测、或基于连通区域检测的方法，由下而上逐步拟合出文本块。&lt;/p&gt;
 &lt;p&gt;因为构造特征的过程偏定制，很难针对不同的文档类型形成大而全的普适方案。在具有特定结构规律的OCR领域，尤其是卡证票据这类常见检测应用场景，传统的模板+滑动窗口定位检测的方法是管用的。&lt;/p&gt;
 &lt;p&gt;近10年来随着深度学习技术的飞速发展，多种多样的神经网络结构（如称为XXNET或XXNN）的检测效果明显优于传统人工构造的特征，当前主流的检测技术由深度学习来主导。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;文本检测中常见的思想有两大类，一类是基于回归的方法，另一类是基于分割的方法。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;基于回归的检测方法，基本思路是先利用若干个默认锚点（Anchor），然后想办法进行合并形成文字框box。2016年ECCV发表的  &lt;strong&gt;CTPN&lt;/strong&gt;是基于回归思想的经典技术（Detecting Text in Natural Image with Connectionist Text Proposal Network，论文出自中科院，为我们中国研究者点赞）。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/5f8f4f3b-0218-4c8e-89fd-c5aee9b99f2a.jpg"&gt;&lt;/img&gt;CTPN是基于回归的文本检测中的经典方法&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;CTPN综合了CNN和LSTM的网络特性&lt;/strong&gt;，在假设文本已经是水平横向分布的前提条件下，做了以下步骤的操作：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;VGG16位backbone提取空间特征，取conv5层输出特征&lt;/li&gt;
  &lt;li&gt;在垂直vertical方向进行feature map，并进行reshape&lt;/li&gt;
  &lt;li&gt;引入Bi-LSTM，从而更好地利用文字连贯性的特征来提升检测效果&lt;/li&gt;
  &lt;li&gt;使用类似Faster R-CNN的RPN网络获得text proposals&lt;/li&gt;
  &lt;li&gt;对获得的大量text proposals，使用NMS（Non-Maximum Suppression，非极大值抑制），或改进后的Soft NMS，Weighted NMS等，过滤和合并文本框。（这个阶段的工作和通用的目标检测任务相似）&lt;/li&gt;
  &lt;li&gt;对得到的水平方向的文本小框合成一个完整的横向文本行，并针对少量倾斜情况做一些矫正&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;CTPN综合了上述若干种网络结构的优点，有优秀的检测效果，尤其对边框矩形的四个顶点的识别很准确，对OCR检测技术的后续发展有承前启后的意义。例如此后的SegLink算法沿用了CTPN的思想，并引入了SSD和旋转角度学习的方法，来解决CTPN遗留的多角度文本检测的问题。&lt;/p&gt;
 &lt;p&gt;基于回归的方法对相对工整（横平竖直）的书面文档文本的检测效果很好，但对各类自然场景下的文本的检测效果难以保障（例如各类弯曲形变的店铺招牌）。所以有另一类思想是源于图像分割（image segmentation）的方法来进行文本检测，即：&lt;/p&gt;
 &lt;p&gt;先从像素层面做分类，判别每一个像素点是否属于一个文本目标，得到文本区域的概率图，然后利用polygon等来绘制出这些候选区域的最小包围曲线，相当于把一堆散落的像素块像串联珍珠那样，链接到一起来形成边界框。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/59b97da8-2861-4591-afad-758a5bf036c3.jpg"&gt;&lt;/img&gt;PSENet网络是基于分割的文本检测技术，对明显弯曲的文本有良好效果&lt;/p&gt;
 &lt;p&gt;基于分割的方面近年优秀的成果包括2019年南京大学等组成的研究团队发表于CVPR的PSENet网络，通过渐进式的尺度扩张网络（Progressive Scale Expansion）来学习文本分割区域，其主干网络本质是ResNet，通过使用不同尺度的Kernel，预测不同收缩比例的文本区域，并逐个扩大检测到的文本区域。&lt;/p&gt;
 &lt;p&gt;PSENet的实质是边界学习方法的变体，可以有效解决任意形状相邻文本的检测问题。具体如网络结构和检测效果如上图所示。&lt;/p&gt;
 &lt;p&gt;2021年华南理工大学在CVPR提出的FCENet，提出了用傅里叶变换来对文本外围的包络线进行参数表示的方法，通过设计合适的模型预测来拟合任意形状文本包围框，从而实现自然场景文本检测中对于高度弯曲文本实例的检测精度的提升。&lt;/p&gt;
 &lt;p&gt;在图像处理和模式识别界最近几年知名国际学术会议，如CVPR、ICCV，AAAI或ICDAR上，每年都有一些最新的网络改进模型被提出（且大量优秀成果都来自中国本土的科研团队，可喜可贺），以下是几篇值得延伸阅读的论文。&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;em&gt;CTPN（Detecting Text in Natural Image with Connectionist Text Proposal Network，ECCV2016）&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;em&gt;SegLink（Detecting Oriented Text in Natural Images by Linking Segments，CVPR2017）&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;em&gt;EAST（EAST: An Efficient and Accurate Scene Text Detector，CVPR2017）&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;em&gt;PSENet（Shape Robust Text Detection with Progressive Scale Expansion Network，CVPR2019）&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;em&gt;DBNet（Real-time Scene Text Detection with Differentiable Binarization，AAAI2019）&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;em&gt;FCENet（Fourier Contour Embedding for Arbitrary-Shaped Text Detection，CVPR2021）&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;strong&gt;文本识别技术   &lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;CRNN网络（循环卷积神经网络）是识别领域里最为经典的方法，直至今日仍然被广泛使用。CRNN网络的技术思想是用深度卷积Convolutional来生成图像基础特征，再使用Bi-LSTM循环网络（双向长短时记忆网络，能吸收上下文语义信息）进行时序特征训练（这一步利用文本序列的前后特征能有效提升效果），最后引入CTC损失函数来实现端对端的不定长序列识别，解决训练时字符无法对齐的问题。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;论文原文见：An End-to-End Trainable Neural Network for Image-based &lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;Sequence Recognition and Its Application to Scene Text Recognition，值得一提的是CRNN由华中科技大学白翔老师团队提出，在OCR领域是极为优秀的研究成果。&lt;/p&gt;
 &lt;p&gt;近年来随着Attention机制在NLP领域取得了很好的效果，将CRNN和Attention结合也成为OCR识别的新思路，在CRNN网络输出层之后加上attention机制，把GRU网络的输出作为encoder的输入，对其做attention，并通过softmax输出，也有非常优异的效果。&lt;/p&gt;
 &lt;p&gt;此处特别值得推荐的是来自NAVER的OCR团队hwalsuklee同学Github上汇总的OCR知识库：https://github.com/hwalsuklee/awesome-deep-text-detection-recognition&lt;/p&gt;
 &lt;p&gt;其中包括了近几年学术界在OCR检测和识别领域的一些知名论文、ICDAR的数据测试集评分和部分开源代码，对系统性了解学术界在OCR领域的成果很有帮助，推荐感兴趣的朋友们阅读，是一个非常好的资料学习库。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;OCR的工程应用开发：从OCR到OCR Pro&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在当前学术圈，OCR研究的热点集中在室外自然场景下的应用。因为这些工作的难度高，效果还不太好，商业化应用还在早期，所以学术研究很热（技术成熟并大量工程应用的领域反而理论研究就会变少），每年都有很多论文发表。这些自然场景STR工作其实和自然语言理解的关系不大，而和计算机视觉（CV）更接近，尤其是通用目标检测（Object Detection）。甚至很多自然场景下的OCR算法干脆就借用了end-to-end的通用框架，例如Yolo这类大名鼎鼎的通用检测系统。&lt;/p&gt;
 &lt;p&gt;而在实际OCR的落地应用界，“卡证票据”的识别已经非常成熟，所以当前的应用热点集中在无固定格式文档的识别和理解上。&lt;/p&gt;
 &lt;p&gt;下图解释了有固定格式的“卡证票据”的处理，和无固定格式的文档资料处理的差异。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/f9571d80-05a0-4303-b95d-dd04585b1463.jpg"&gt;&lt;/img&gt; 无固定格式的文档资料OCR是当前应用的热点和难点&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;“卡证票据”&lt;/strong&gt;的特点是格式相对明确和固定（例如身份证，姓名、性别、身份证号等各个信息的位置是明确的），所以检测（Detection）和识别（Recognition）要容易的多，通过设定模板来检测定位，进而进行文字识别来输出各类Key：Value型的结构化抽取结果是相对容易的。&lt;/p&gt;
 &lt;p&gt;而我们日常办公所经常面对的  &lt;strong&gt;无固定格式的文档资料&lt;/strong&gt;，因为版式变化多样，需要进行提取的内容可能分布在不同的位置，因而很难简单用模板匹配的方式来进行检测、识别、抽取三个动作，需要更复杂的操作步骤，我们称为OCR Pro系统。&lt;/p&gt;
 &lt;p&gt;针对无固定格式的OCR Pro系统，一个待处理的文档图片（扫描件或手机翻拍件）要依次进行版面分析（Layout Analysis），文字识别（Recognition）、信息提取（Intelligent Document Processing）、行业知识校验（Domain Knowledge Recheck）等步骤。&lt;/p&gt;
 &lt;p&gt;使用版面分析的原因是日常办公文档的构成元素非常复杂，不仅有常见的文字块，还会出现标题、目录、印章、签名、表格、图例、页眉页脚等各类元素，版面分析技术的目的就是要通过页面各类元素信息的视觉特征、结合文本语义特征和各类embedding信号，将文档“庖丁解牛”分解为若干元素，为后续的识别和结构化抽取打好基础。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/cb280c8e-f9d4-4779-a1cb-195410064de7.jpg"&gt;&lt;/img&gt;版面分析技术在OCR和IDP中有重大意义&lt;/p&gt;
 &lt;p&gt;在版面分析中，有一类常见且重要的特殊元素——表格。因为OCR的最终目的是将文档里最关键的内容自动化提取出来，表格中往往包含非常密集的重要信息，因此对表格的解析和语义理解技术相对更为特殊，在后面的章节中将进一步对该技术进行详细介绍。&lt;/p&gt;
 &lt;p&gt;和自然场景下（in the wild）的OCR检测不同，针对办公文档的OCR因为文字的横竖布局以及背景图片质量明显比自然场景的图片要高得多，也用不着太多来自于通用目标检测（Object Detection）的技巧。在办公文档OCR的实际产业应用里，版面分析技术事实上代替了文本检测技术发挥实际作用。&lt;/p&gt;
 &lt;p&gt;文档处理环节里的文字识别则技术相对成熟，因为大部分文字都以打印字符的形式存在，通常情况下这个环节下的文字识别技术已经非常成熟和准确了。当前达观的一些研究主要分布在以下一些相对特殊的场合：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;01 &lt;/strong&gt;  &lt;strong&gt;去除文档的底纹或水印干扰&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;工作文档采用带有底纹的特殊纸张，或者有的人为打上水印（例如一些重大项目的投标书）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;02 &lt;/strong&gt;  &lt;strong&gt;提取和理解关键性元素&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;书面文本处理中存在一些特殊种类的元素，例如印章、手写签名等元素就是国内（包括东亚地区各国家）合同里极为重要的组成部分，需要专门的模型进行处理。而且还需要对印章的文字内容进行提取和识别（通常为圆形），并用于后续和合同里签署主体进行对比审核。手写签名提取后也会用于进行比对。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;03 &lt;/strong&gt;  &lt;strong&gt;识别和处理特殊符号&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;书面文档中经常有一些表达语义的专用符号，如√（对勾）、编号①、角标（常见于注释提示）、下标（常见于数理化公式）等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;达观OCR的工程化实践&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;要开发出真正可以落地使用的OCR产品，需要面对真实使用情况里多种多样的问题。这些问题往往显得非常的琐碎，但是只有实实在在把这些琐碎的问题解决好，才能让产品落地好用。&lt;/p&gt;
 &lt;p&gt;就以最为常见的表格的OCR解析为例，其实我们日常文档中遇到的表格情况非常多，以下图为例，这些表格存在各类分栏，水印、跨页、揉搓、阴影、印章遮挡等各种各样的问题，需要逐一有技术来应对。（达观数据陈运文）&lt;/p&gt;
 &lt;p&gt;另外表格中还存在无边框表格（常见于一些上市公司财报），或单元格嵌套的复杂表格（常见于一些复杂行政审批事项填报表），都需要进行处理。如下图所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/24a3619f-704f-42a2-bc9f-f1c32d50523c.jpg"&gt;&lt;/img&gt;达观数据对表格的OCR处理和语义理解&lt;/p&gt;
 &lt;p&gt;近年来基于深度学习的表格检测和识别算法在ICDAR（International Conference on Document Analysis and Recognition）会议上有很多原创性的成果，如A Genetic-based Search for Adaptive Table Recognition in Spreadsheets论文所提出的方法，将表格中的单元格分为Header、Data和Metadata等类型，然后相邻单元格根据标签异同组成不同的区域，这些区域根据相邻关系则构成了一个标签区域图，巧妙的将表格结构识别任务变成了子图分割任务，方法接着定义了将10个衡量因素加权求和来评判分割质量，用于确定优化目标。然后使用序列二次规划的方法来自动调节权重，并综合运用了遗传算法和一些启发式方法、或穷举搜索等来进行最优化。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/cf7d77d1-ce03-451f-a15b-397dd27b8e24.jpg"&gt;&lt;/img&gt;ICDAR中基于深度学习的表格检测和识别算法&lt;/p&gt;
 &lt;p&gt;近年来通过ICDAR的技术竞赛，有一些优秀的方法涌现出来，在这个领域里国内也出现了很多研究团队，如北京大学的高良才老师提出了很多优秀的研究成果。&lt;/p&gt;
 &lt;p&gt;和一些直接套用计算机视觉检测的方法不同，表格因为有横列纵列的重复分布特征，所以利用这个特征来进行识别往往能取得更有针对性的效果，ICDAR2019论文Table structure extraction with Bi-directional Gated Recurrent Unit Networks提出了使用循环神经网络来进行表格结构识别任务。在一系列基础性的二值化和膨胀预处理后，将图像按像素行或列放入独立的两个两层双向循环神经网络，同时将某个像素行或列的相邻两个邻居考虑进去。接着将循环神经网络的输出行列特征分类为是否属于行列分隔符区域，最终把预测分隔区域的中点作为最终的行列分割结果。GRU网络的效果相比LSTM整体略好。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;达观的实践总结与展望&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在达观实践OCR产品过程中，我们发现一旦版面分析工作做扎实后，文本检测就变得很容易了。而完成文本识别后，利用语义上下文技术，对内容进行结构化提取就是IDP系统来完成的工作了，Attention以及NLP的一些模型可以很好发挥长处。&lt;/p&gt;
 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221012/2d31708b-8646-4e2e-b3c1-9b9c8099168f.jpg"&gt;&lt;/img&gt;达观工程化OCR处理流程&lt;/p&gt;
 &lt;p&gt;如上图所示，为了更好地提升效果，充分运用行业知识校验对提升OCR效果也起到了重要作用。学术研究里通常不会涉及外部领域知识，但在实际落地应用中构造专业领域的知识图谱对每一个垂直细分领域的文本OCR任务都有巨大的收益。&lt;/p&gt;
 &lt;p&gt;例如财务报表OCR中，各类数字之间隐含的勾稽关系（如利润表中的“主营业务成本”与资产负债表中的“应付账款”以及现金流浪表中的“购买商务和劳务支出”可以进行交叉校验）可以用于对OCR提取的数字进行校验和纠正，大幅提升准确率。再比如IPO招股书中的企业经营数据，会在相应的审计报告中再次出现，如果引入投行的专业经验，那么对OCR的处理效果会有很大帮助。最后，人工复检工作以及相应的结果自动反馈机制也非常重要，人工复检不仅能让系统最终实现100%的准确率，并且人工纠正后的结果能不断作为训练样本用于矫正原有系统的问题，从而能让系统越来越“聪明”，逐步逼近更高的识别准确率。&lt;/p&gt;
 &lt;p&gt;在达观近年来将IDP、知识图谱和OCR进行融合来进行工程实践的过程中，我们深刻领会到一个优秀的产品一定要实事求是的吸收各种思想的优点，既要有传统方法的长处，也要借鉴最新网络模型的优点。对数据的积累和标注是一个持之以恒的事情，产品的使用细节体验，人机交互的过程也需要不断地完善和提升。好的产品从来都不是一蹴而就的，而是需要反复打磨和持续改进的。随着OCR技术近年来不断向前发展，和各类下游的应用场景，如文档审核、语义理解、RPA等的结合日益增多，OCR的应用还将发挥越来越大的价值。（达观数据陈运文）&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;作者简介&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;陈运文，达观数据董事长兼CEO。复旦大学计算机博士，优秀博士论文奖获得者，国家“万人计划”专家，2021年中国青年创业奖，中国五四青年奖章，上海市十大青年科技杰出贡献奖获得者，上海市优秀技术带头人，第九届上海青年科技英才；国际计算机学会（ACM）、电子电器工程师学会（IEEE）、中国计算机学会（CCF）、中国人工智能学会（CAAI）高级会员，上海市计算机学会多媒体分会副会长；上海市首批人工智能正高级职称获得者。在人工智能领域拥有近百项国家技术发明专利，是复旦大学、上海财经大学、上海外国语学院聘任的校外研究生导师，在IEEE Transactions、SIGKDD等国际顶级学术期刊和会议上发表数十篇高水平科研成果论文，出版《智能RPA实战》、人工智能经典著作《智能Web 算法》（第2 版），参与撰写《数据实践之美》等论著；曾多次摘取ACM KDD CUP、CIKM、EMI Hackathon等世界最顶尖的大数据竞赛的冠亚军荣誉。曾担任盛大文学首席数据官、腾讯文学高级总监、百度核心技术研发工程师。在机器学习、自然语言处理、搜索推荐等领域有丰富的研究和工程经验。&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>自然语言处理 OCR</category>
      <guid isPermaLink="true">https://itindex.net/detail/62449-ocr-%E6%8A%80%E6%9C%AF-%E5%8F%91%E5%B1%95</guid>
      <pubDate>Wed, 12 Oct 2022 19:04:17 CST</pubDate>
    </item>
    <item>
      <title>2022 年 Gartner 新兴技术成熟度曲线的新变化</title>
      <link>https://itindex.net/detail/62411-gartner-%E6%96%B0%E5%85%B4%E6%8A%80%E6%9C%AF-%E6%88%90%E7%86%9F</link>
      <description>&lt;div&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2022 年的新兴技术符合三个主题：不断发展和扩展的      &lt;strong&gt;沉浸式体验&lt;/strong&gt;、加速      &lt;strong&gt;AI自动化&lt;/strong&gt;和优化      &lt;strong&gt;技&lt;/strong&gt;      &lt;strong&gt;术专家交付&lt;/strong&gt;。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;简而言之： &lt;/p&gt;    &lt;p&gt;•2022 年 Gartner 新兴技术技术成熟度曲线 包含 25 项“必须知道”的创新，以推动竞争差异化和效率。&lt;/p&gt;    &lt;p&gt;•只有少数有可能在短短两年内达到被主流采用；许多将需要10年或更长时间。&lt;/p&gt;    &lt;p&gt;•这些技术处于初期，部署它们的风险更大，但对早期采用者的好处可能更大。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2022 年 Gartner 技术成熟度曲线确定了 25 种必须了解的新兴技术，旨在帮助企业架构和技术创新领导者： &lt;/p&gt;    &lt;p&gt;•扩展沉浸式体验 &lt;/p&gt;    &lt;p&gt;•加速AI自动化 &lt;/p&gt;    &lt;p&gt;•优化技术专家交付 &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;这些技术预计将在未来 2 到 10 年内对企业和社会产生巨大影响，尤其是使 CIO 和 IT 领导者能够实现数字业务转型。 &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;新兴技术本质上是颠覆性的，没有众所周知或经过验证的竞争优势。为了抓住机遇，了解潜在用例和技术走向，主流采用的途径至关重要——这可能短至两年，长则10 年或更长时间。 &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;“所有这些技术都处于早期阶段，但有些处于萌芽阶段，它们将如何发展存在很大的不确定性。新兴技术为部署带来了更大的风险，但为早期采用者带来了更大的好处，这使它们与 Gartner 的顶级战略技术趋势区分开来”，Gartner 副总裁分析师Melissa Davis说。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2022 年及以后需要考虑的三个技术成熟度曲线主题&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2022 年 Gartner 技术成熟度曲线以新兴技术为特色，并将 2000 多种技术的见解提炼成简洁的高潜力集合。大多数技术都有多个用例，但企业架构和技术创新领导者应该优先考虑那些为其组织带来最大潜在利益的技术。（他们还需要启动一个概念验证项目，以证明技术对其目标用例的可行性。）&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;以下是关于 2022 年技术所属的三个主题的更多信息：&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;主题 1：发展/扩展沉浸式体验&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;这些技术的好处在于，它们为个人提供了对其身份和数据的更多控制权，并将他们的体验范围扩展到可以与数字货币集成的虚拟场所和生态系统。这些技术还提供了接触客户的新方法，以加强或开辟新的收入来源。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;客户数字孪生 (DToC，Digital Twin Of the Customer ) 是客户的动态虚拟表示，它模拟并学习模拟和预测行为。它可用于修改和增强客户体验 (CX，Customer Experience) 并支持新的数字化工作、产品、服务和机会。DToC 需要 5 到 10 年才能被主流采用，但将对组织产生变革。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;沉浸式体验中的其他关键技术包括：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;1、      &lt;strong&gt;去中心化身份&lt;/strong&gt;(DCI，Decentralized Identity) 允许实体（通常是人类用户）通过利用区块链或其他分布式账本技术 (DLTs，Distributed Ledger Technologies) 等技术以及数字钱包来控制自己的数字身份。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2、      &lt;strong&gt;数字人类&lt;/strong&gt;（Digital Humans）是交互式的、由AI驱动的表示，具有人类的一些特征、个性、知识和思维方式。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;3、内部人才市场（Internal Talent Marketplaces）将内部员工以及在某些情况下的临时员工池与有时间限制的项目和各种工作机会相匹配，而无需招聘人员参与。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;4、      &lt;strong&gt;元宇宙&lt;/strong&gt;（Metaverse）是一个集成的虚拟 3D 共享空间，由虚拟增强的物理和数字现实的融合创建。元宇宙是持久的，提供增强的沉浸式体验。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;4、      &lt;strong&gt;NFT&lt;/strong&gt;(Non-Fungible Token) 是一种基于区块链的独特可编程数字项目，可公开证明数字资产（如数字艺术或音乐）或代币化的物理资产（如房屋、汽车或文件）的所有权。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;5、Superapp是一个复合移动应用程序，作为平台构建，可提供模块化微应用程序，用户可以激活这些微应用程序以获得个性化的应用程序体验。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;6、      &lt;strong&gt;Web3&lt;/strong&gt;是用于开发分散式 Web 应用程序的新技术堆栈，使用户能够控制自己的身份和数据。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;https://vid.gartner.com/watch/C3MAVBuZRFVxo4Vyeeqknt?autoplay=2&amp;amp;second=0&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;主题 2：加速AI自动化&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;扩大AI的采用是发展产品、服务和解决方案的关键方式。这意味着加速创建专门的AI模型，将AI应用于AI模型的开发和训练，并将其部署到产品、服务和解决方案的交付中。结果包括更准确的预测和决策以及更快地获得预期收益。人类的角色也更侧重于成为消费者、评估者和监督者。 &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;自主系统是加速AI自动化的例子。它们是自我管理的物理或软件系统，执行具有三个基本特征的域界任务：自主、学习和代理。当传统的AI技术无法实现业务适应性、灵活性和敏捷性时，自主系统可以成功地帮助实施。自主系统需要 5 到 10 年才能被主流采用，但将对组织产生变革。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;加速 AI 自动化的其他关键技术包括： &lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;1、因果AI识别（Causal artificial intelligence）并利用因果关系超越基于相关性的预测模型，转向可以更有效地规定行动并更自主地行动的AI系统。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2、基础模型是基于转换器架构的模型，例如大型语言模型，它体现了一种深度神经网络架构，可以在周围单词的上下文中计算文本的数字表示，强调单词的序列。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;3、生成式设计（Generative design）AI或 AI 增强设计，是使用 AI、机器学习 (ML) 和自然语言处理 (NLP) 技术自动生成和开发数字产品的用户流程、屏幕设计、内容和表示层代码。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;4、机器学习代码生成工具包括可插入专业开发人员集成开发环境 (IDE) 的云托管 ML 模型，IDE 是基于自然语言描述或部分代码片段提供建议代码的扩展。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;主题 3：优化技术人员交付&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;这些技术专注于构建数字业务的关键要素：产品、服务或解决方案构建者社区（如融合团队）及其使用的平台。这些技术提供反馈和洞察力，优化和加速产品、服务和解决方案的交付，并提高业务运营的可持续性。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;云数据生态系统体现了优化的技术人员交付。它们提供了一个有凝聚力的数据管理环境，能够支持从探索性数据科学到生产数据仓库的所有数据工作负载。云数据生态系统提供简化的交付和全面的功能，易于部署、优化和维护。它们将需要两到五年的时间才能被主流采用，并且对用户非常有益。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;优化技术人员交付的其他关键技术包括：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;1、将敏捷性、持续集成和部署以及最终用户对财务治理、预算编制和成本优化工作的反馈等传统 DevOps 进一步自动化。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;2、云可持续性（Cloud sustainability）是使用云服务在经济、环境和社会系统中实现可持续性效益。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;3、计算存储(CS，Computational Storage) 将主机处理从中央处理单元 (CPU) 的主存储器卸载到存储设备。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;4、网络安全网格架构(CSMA，Cybersecurity Mesh Architecture ) 是一种新兴的方法，用于构建可组合的分布式安全控制，以提高整体安全效率。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;5、数据可观察性（Data observability）是通过持续监控、跟踪、警报、分析和故障排除事件来了解组织数据环境、数据管道和数据基础设施健康状况的能力。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;6、动态风险治理(DRG，Dynamic Risk Governance) 是一种新方法，用于定义风险管理的角色和责任这一关键任务。DRG 针对每种风险适当地定制风险治理，使组织能够更好地管理风险并降低保证成本。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;7、行业云平台利用底层 SaaS、平台即服务 (PaaS) 和基础架构即服务 (IaaS) 云服务，为确定的垂直行业提供与行业相关的打包业务和技术能力作为一个整体产品。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;8、最小可行架构(MVA，Minimum Viable Architecture) 是产品团队用来确保及时、合规地开发和迭代产品的标准化框架。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;9、可观察性驱动开发(ODD，Observability-Driven Development) 是一种软件工程实践，通过将系统设计为可观察的，为系统状态和行为提供细粒度的可见性和上下文。 &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;10、OpenTelemetry是规范、工具、应用程序编程接口 (API) 和软件开发工具包 (SDK) 的集合，用于描述和支持软件的开源仪器和可观察性框架的实施。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;11、平台工程（Platform engineering）是为软件交付和生命周期管理构建和运营自助式内部开发人员平台 (IDP，Internal Developer Platforms) 的学科。&lt;/p&gt;    &lt;p&gt;   &lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt; &lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62411-gartner-%E6%96%B0%E5%85%B4%E6%8A%80%E6%9C%AF-%E6%88%90%E7%86%9F</guid>
      <pubDate>Thu, 08 Sep 2022 12:31:42 CST</pubDate>
    </item>
    <item>
      <title>技术管理者的 4 个基本思考点</title>
      <link>https://itindex.net/detail/62384-%E6%8A%80%E6%9C%AF-%E7%AE%A1%E7%90%86-%E6%80%9D%E8%80%83</link>
      <description>&lt;p&gt;技术团队管理者在日常工作中可能经常会遇到如下一些状况：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;自测质量差&lt;/li&gt;
  &lt;li&gt;转测 BUG 多&lt;/li&gt;
  &lt;li&gt;项目延期&lt;/li&gt;
  &lt;li&gt;加班赶工&lt;/li&gt;
  &lt;li&gt;高强度加班后，小伙伴状态不好，导致更多的问题出现&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;从第 1 点状况演变成第 5 种状况，第 5 点状况继续推动第 1 种状态的持续加强，从而导致整个团队的状态极差，陷入 BUG 多 –&amp;gt; 延期 –&amp;gt; 加班 –&amp;gt; BUG 更多 –&amp;gt; 更多的延期 的死循环。&lt;/p&gt;
 &lt;p&gt;除了上面的死循环，可能还会有一些非功能性的问题，如性能、扩展性问题等等。&lt;/p&gt;
 &lt;p&gt;当团队大时，还可能遇到有小团队，各小团队各行其事，各为其主，心不往一处，力不出一孔。 这些问题让技术团队的管理者焦头烂额。&lt;/p&gt;
 &lt;p&gt;那么如何解决这些问题呢？个人认为可以从以下 4 个方面来逐一思考和优化，从而在一定程度上解决这些问题。&lt;/p&gt;
 &lt;h1&gt;1. 把正确的人放到合适的岗位&lt;/h1&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;成本太高，即使这个人具备可培养性，但从 0 到 1 把一个人培养起来，时间成本太高，而管理者的时间很贵，公司等不起。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;所以我们这里是选人，从现有的人中找到合适的，从人才市场找到合适的，能直接用的。&lt;/p&gt;
 &lt;p&gt;本小节主要是回答两个问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;正确的人是怎样的？&lt;/li&gt;
  &lt;li&gt;如何把正确的人放到合适的岗位上？&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;1.1 选人&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;li&gt;宁缺毋滥：如果实在没有人，宁缺毋滥吗？这是一个好问题，严格来说是这样的，但实际中往往我们会妥协一部分。&lt;/li&gt;
&lt;/ul&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;li&gt;能扛事，遇到事情找方法，不找借口，执行力强；&lt;/li&gt;
  &lt;li&gt;有所为，天赋决定了能达到的上限，努力程度决定了能达到的下限。努力去做，有所为，并且 以现在绝大多数人的努力程度之低，还远远没有达到比拼天赋的程度。 这里的有所为不仅仅是做事，更多的是学习，不停地学习，提升自己。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;1.2 用人&lt;/h2&gt;
 &lt;h3&gt;1.2.2 人才梯队&lt;/h3&gt;
 &lt;p&gt;人才梯队从时间上看分为现在和将来。 现在是指盘点现有人才情况，梳理人才结构，对团队中的人进行分层，形成「梯状」。&lt;/p&gt;
 &lt;p&gt;当前状态的人才梯队分为两个层面，一个是分层，另一个是分层后的职责。 当团队大一些后，需要明确团队的组织分层，这里可能是正式认命的 Leader，也可能是虚拟的负责人，不管是实的还是虚的，最终都会有一个层存在。&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;当现在的人才正在发挥作用时，培养接班人，我们经常称之为 B 角。当有人才变动时可以快速补位上去。常用的方法有内部培养，跨团队轮岗，外部招聘等。&lt;/p&gt;
 &lt;p&gt;这块特别狠的可能要算宇宙厂了，在现有人员已经能满足工作需要的同时，会继续招聘，如果更优，可能会换人。&lt;/p&gt;
 &lt;h3&gt;1.2.2 艰难的决定&lt;/h3&gt;
 &lt;p&gt;在我们构建人才梯队的时候，想要做到知人善任是一件很难的事情，并且让每个人的表现都达到预期水平更难。当有些人并不能胜任他当前的工作时，我们应该怎么做？这里可能有以下两种常见的方式：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;调岗，一般这种情况可能只是人岗不匹配，或者有些变化跟不上，但是对于人的部分能力还是比较认可。此时我们在公司内给他找一个匹配的岗位，更好地发挥其潜力。在技术团队最常见的例子是有些高 P 的技术同学，被推到管理岗，过了一段时间，发现适应不了，此时我们可以将其调到更能发挥其能力的岗位上来。又或者一些管理者因为团队扩大或者一些其它原因，管理的范围一下子增加了很多，一段时间后发现无法搞定这样的团队，此时可能将其调整到稍微低一级的职位上更合适一些。&lt;/li&gt;
  &lt;li&gt;离开，尽量让对方体面的离开，公司和个人都体面的分手，该赔偿的赔偿，该理解的理解，如果在外面有合适的机会，顺手推一把，也是个不错的善缘，不枉同事一场。这里经常出现的问题是犹豫不决，最终导致大家都不开心，不欢而散。管理上常说：「心要慈,刀要快」，就是要规避这种犹豫。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;1.3 管理者自己&lt;/h2&gt;
 &lt;p&gt;在用人中还包括管理者自己，一个技术管理者，不仅仅要注重技术，不仅仅要设定明确的目标并排出优先顺序，跟进过程发现问题解决问题，拿到结果，还要注意另一个重要的点：业务参与者。&lt;/p&gt;
 &lt;p&gt;一个优秀的管理者和一个不那么优秀的管理者的主要差别就是他们对业务的参与程度。事实证明，对所负责业务参与的程度越深，你就越能做出更加明智的决策。&lt;/p&gt;
 &lt;p&gt;什么叫业务参与度？&lt;/p&gt;
 &lt;p&gt;我认为它不是事无巨细地了解进展，应该到一线去，怎么到一线去，去写代码，去做代码评审？&lt;/p&gt;
 &lt;p&gt;到一线去，有两个层次，一个是业务的一线，用户或产品，看用户的反馈，产品的实现，二是工作的一线，看工作流程的卡点，系统化的情况，可视化的情况。不要自己去做这些事情，主要是收集信息，决策或者发现问题解决问题，这里确定做什么的原则是： 你做的事情的价值大小，而价值大小可以从时间杠杆，团队杠杆上来指数级扩大，如果做一件事只有短期的一件事的价值，那这些事情就不是你应该做的，应该停下来去思考要做的事情。&lt;/p&gt;
 &lt;h1&gt;2. 组织&lt;/h1&gt;
 &lt;p&gt;说到组织，你是想要一个「令行禁止，使命必达」的组织，还是一个「简单可依赖」的组织？&lt;/p&gt;
 &lt;p&gt;组织是为实现共同目标而采取的一种分工协作体系，是人与人之间的关系，组织结构往往会随着组织的重大战略调整而调整。&lt;/p&gt;
 &lt;p&gt;而企业在商场中求生，随着外部环境的变化，行业的变化，内部环境的演进而会不断进行迭代，不断调整组织结构，因此我们经常需要重新设计组织结构。&lt;/p&gt;
 &lt;h2&gt;2.1 设计组织结构&lt;/h2&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;li&gt;组织的哪些部分应该结合在一起，哪些部分应该分开？&lt;/li&gt;
  &lt;li&gt;组织内各单元之间的关系和协同应该是怎样的?&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;设计组织结构最难的问题可能是分和合的问题，这里有一个原则： 凡是做出同样的贡献的活动可以结合在一个部门中统一管理，不论它们的技术专业是什么。那些不是做出同样贡献的活动则一般不应合在一起。&lt;/p&gt;
 &lt;p&gt;在考虑组织结构，特别是研发团队的组织结构的时候，千万不可忽略了康威定律，甚至我们有时需要「逆康威定律」，通过适配康威定律，在明确技术架构方向的基础上，以组织结构的调整来推动技术架构的演进。&lt;/p&gt;
 &lt;h2&gt;2.2 组织的形态&lt;/h2&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;/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;以上 4 个权限很容易让我们想到项目管理里面的 RACI 矩阵：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;谁负责：（R = Responsible），即负责执行任务的角色，他/她具体负责操控项目、解决问题。&lt;/li&gt;
  &lt;li&gt;谁批准：（A = Accountable，决策权），即对任务负全责的角色，只有经他/她同意或签署之后，项目才能得以进行，是整个事情的决策者；&lt;/li&gt;
  &lt;li&gt;咨询谁：（C = Consulted，建议权），拥有完成项目所需的信息或能力的人员，多提出建议。&lt;/li&gt;
  &lt;li&gt;通知谁： (I =Informed，知情权)，即应及时被通知结果的人员，却不必向他/她咨询、征求意见。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;以上四种权力，决策权和建议权是权力主线，审核权和知情权是权力副线。主线是上下级逻辑，副线是平级或流程型逻辑，而我们往往理解的权力是主线权力。&lt;/p&gt;
 &lt;p&gt;在强权力主线的基础上，权力型组织的有以下 3 个缺点：&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;p&gt;为规避这些缺点我们需要减少层级，增加连接和协同。&lt;/p&gt;
 &lt;p&gt;如果把管理层只分为三级，我们一般称之为高层、中级和基层。这三个层要解决的问题不同。高层解决长期发展的问题，中层解决系统效率的问题，基层解决的是事情本身的问题。&lt;/p&gt;
 &lt;p&gt;比如技术管理者常规上可以分为三个大层：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;高层管理：解决长期发展的问题，使企业/团队有前途，关注的是未来，不管是技术发展的未来，还是业务发展的未来；&lt;/li&gt;
  &lt;li&gt;中层管理：解决系统效率的问题，使系统更有效率，要不断强化研发流程的统一性和适应性，确定并执行好标准和规范，提升协同的效率，标准化和系统化，同时为上层的选择和决策提供依据和保障；&lt;/li&gt;
  &lt;li&gt;基层管理：解决事情本身的问题，而事情往往会落到一线的开发同学身上，于是经常我们基层管理者的主要工作是使一线开发同学工作有成就，培养一线开发同学，提高他们承担工作的能力和意愿，帮助他们解决问题。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们期望是每个层能做好自己的事情，但是实际上往往是高层在做中层的事情，中层在做基层的事，基层在做一线的事情，错位了。于是，我们需要将管理层归位，各行其职，更专业的做事情。&lt;/p&gt;
 &lt;p&gt;在明确职责的基础上，尽量减少管理的层级，尽量不要出现 「副XX」 的岗位。&lt;/p&gt;
 &lt;p&gt;在协同方面我们用流程来解决协同合作的问题。流程本身的逻辑是对事情负责，对自身的任务及向下事项/环节负责。其驱动力是责任感和依赖。百度文化里面的「简单可依赖」能较好地说明流程的底层逻辑。当我们使用流程来解决协同的问题时，要经常迭代流程以优化协同的效率：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;注意流程是为了解决过程中的问题，不是为了设置卡点，每个人都有扩大自己权力的欲望，这是我们在流程建设中要特别注意的；&lt;/li&gt;
  &lt;li&gt;尽量减少流程环节，聚合责任主体，我们经常遇到一个事情在某职能部门还需要经过两三道审核的情况，这种损耗可以通过聚合责任主体的方式来解决；&lt;/li&gt;
  &lt;li&gt;控制流程时长，这里控制流程时长还包括流程环节的前置准备，就和我们开会一样，如果开会前准备充分，会议本身的时长就有极大可能减少。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;用权力解决「力出一孔」的问题，用流程解决协同合作的问题。&lt;/p&gt;
 &lt;p&gt;简单来说，在权力体系的基础上，用流程连接各环节和负责主体，现在比较常见的矩阵式组织结构基本符合这个逻辑。当然这里有一个以流程为主还是以权力为主的问题，具体是哪种，还得看公司当前组织结构的逻辑。&lt;/p&gt;
 &lt;h2&gt;2.3 分工的粗和细&lt;/h2&gt;
 &lt;p&gt;Adam Smith 在 1776 年的《国富论》中提出了分工，分工对于手工业生产效率有较大的提高，其总结了三个优点：&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;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;分工落到一个研发团队，我们通常按编程语言分为 Java、PHP、C++、Javascript，或按端分为安卓端、iOS 端、前端、后端、算法、数据等，又或者大一点分为开发、测试、运维，架构师。&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;p&gt;更极端一些，走全栈路线，一个人从头到尾完成需求的所有工序。但是这种方式，对于人员素质的要求，对于团队组织的要求和常规不一样，且人的精力是有限的，能在每个方面都做到精通的，少之又少，除非所做的事情只需要略懂即可。&lt;/p&gt;
 &lt;p&gt;思考一下，你所在团队应该如何来做？&lt;/p&gt;
 &lt;p&gt;成本和效率？组织大小？技术发展？架构演进？&lt;/p&gt;
 &lt;h1&gt;3. 机制&lt;/h1&gt;
 &lt;h2&gt;3.1 DRI 机制&lt;/h2&gt;
 &lt;h3&gt;3.1.1 DRI 的由来&lt;/h3&gt;
 &lt;p&gt;DRI 是 Directly Responsible Individual 的简称，中文翻译为「直接负责人」。最开始是从苹果流传出来的内部管理概念。&lt;/p&gt;
 &lt;p&gt;DRI 不是流程、过程，也不是框架，而是一个负责人，对某部分的整体负责，小到 BUG，大到技术方向。 DRI 是为了解决责任主体的问题，其有助于避免责任分散。责任分散这个概念也被称为「旁观者效应」，也就是人们身处团队中时无法对某事负起责任，责任分散到了团队中的每个成员身上，而不是集中在真正有责任的人身上，因为每个人认为那个责任应该由其他人承担，表现得像一个旁观者。&lt;/p&gt;
 &lt;h3&gt;3.1.2 DRI 的职责&lt;/h3&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;统筹策划，搞定所有的干系人，从头到尾负责到底，说简单点就是团队成员专注的做好手上的事儿， DRI 排除干扰，解决各种烦人的问题来，发现问题，解决问题；&lt;/li&gt;
  &lt;li&gt;有一定的领导责任。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;简单来说，当你是某个事情的 DRI 后，这个事情就是你自己的事情。特别是当职责不清或者突发问题时，DRI 就需要发挥主人翁的精神，拉起团队成员去分析问题，解决问题。&lt;/p&gt;
 &lt;h3&gt;3.1.3 什么人适合做 DRI&lt;/h3&gt;
 &lt;p&gt;DRI 和工作年限无关，和是否资深无关，和技术工种无关，你想你就是。&lt;/p&gt;
 &lt;p&gt;但是在操作过程中，我们会根据实际的场景做一些偏重。如果是一个后台的活儿居多的项目，其 DRI 大概率是后台的开发同学，如果是一个质量问题较多的项目，其 DRI 大概率是一个 QA 同学。&lt;/p&gt;
 &lt;p&gt;这里我们会充分考虑同学的主观意愿，有些同学想，有些同学不愿意，只想做好手上的工作，那么他做好他手上工作的 DRI 就可以了。&lt;/p&gt;
 &lt;p&gt;DRI 无关项目大小，无关职位高低，无关所在层级，每一件大事，小事都需要有一个 DRI，且只有一个 DRI&lt;/p&gt;
 &lt;p&gt;这玩意儿换成中文其实就是我们在标语里面经常看到的「责任到人」差不多，但是更强调责任主体的唯一性。&lt;/p&gt;
 &lt;h2&gt;3.2 构建良好的协同机制&lt;/h2&gt;
 &lt;p&gt;公元前 221 年，秦始皇用了十年的时间，先后灭了韩、赵、魏、楚、燕、齐六国，完成了统一中国的大业。在以后，他陆续颁布了多条律法，以稳固国家的统治，包括「书同文」、「车同轨」、「度同制」等。&lt;/p&gt;
 &lt;p&gt;一个国家，一个组织，要想成为一个高效执行的团队，一定要有标准，一定要有人告诉大家怎样做是对的。 落到我们团队管理，标准，流程是必须要做的，特别是当你的团队是由若干个团队整合或融合的时候。&lt;/p&gt;
 &lt;h3&gt;3.2.1 统一标准规范&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;/p&gt;
 &lt;p&gt;标准不是为了成功，而是为了让整个事情不至于太坏，尽量不出现重大的问题。 具体到研发团队，我们一般需要统一如下一些标准：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;研发过程
   &lt;ul&gt;
    &lt;li&gt;代码风格规范&lt;/li&gt;
    &lt;li&gt;数据库设计规范&lt;/li&gt;
    &lt;li&gt;代码分干管理规范&lt;/li&gt;
    &lt;li&gt;代码提交规范&lt;/li&gt;
    &lt;li&gt;错误码规范&lt;/li&gt;
    &lt;li&gt;Code Review 标准&lt;/li&gt;
    &lt;li&gt;代码权限管理规范&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;沟通协同
   &lt;ul&gt;
    &lt;li&gt;架构规范&lt;/li&gt;
    &lt;li&gt;技术方案规范&lt;/li&gt;
    &lt;li&gt;文档规范&lt;/li&gt;
    &lt;li&gt;接口规范&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;质量标准
   &lt;ul&gt;
    &lt;li&gt;代码质量标准&lt;/li&gt;
    &lt;li&gt;自动化测试标准&lt;/li&gt;
    &lt;li&gt;测试质量标准&lt;/li&gt;
    &lt;li&gt;线上质量标准&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;性能标准
   &lt;ul&gt;
    &lt;li&gt;服务端性能标准&lt;/li&gt;
    &lt;li&gt;客户端性能标准&lt;/li&gt;
    &lt;li&gt;前端性能标准&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;安全标准
   &lt;ul&gt;
    &lt;li&gt;信息安全标准&lt;/li&gt;
    &lt;li&gt;代码安全标准&lt;/li&gt;
    &lt;li&gt;数据安全标准&lt;/li&gt;
    &lt;li&gt;线上安全标准&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;3.2.2 统一流程&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;/p&gt;
 &lt;p&gt;我们做任何一件事都是有流程的，有些是设计过的，有些是自然而然的，设计过的流程可能是别人的经验。并且流程需要持续迭代。&lt;/p&gt;
 &lt;p&gt;在研发管理中我们常常会构建的流程如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;敏捷流程
   &lt;ul&gt;
    &lt;li&gt;需求迭代流程&lt;/li&gt;
    &lt;li&gt;紧急需求流程&lt;/li&gt;
    &lt;li&gt;值班需求流程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;研发流程
   &lt;ul&gt;
    &lt;li&gt;代码审核流程&lt;/li&gt;
    &lt;li&gt;代码发布流程&lt;/li&gt;
    &lt;li&gt;紧急发布流程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;协同
   &lt;ul&gt;
    &lt;li&gt;对接流程&lt;/li&gt;
    &lt;li&gt;资源申请流程&lt;/li&gt;
    &lt;li&gt;线上问题/告警处理流程&lt;/li&gt;
    &lt;li&gt;事故处理流程&lt;/li&gt;
    &lt;li&gt;安全问题处理流程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;3.2.3 统一工具&lt;/h3&gt;
 &lt;p&gt;以上说了要统一标准，统一流程，这些第一步是要把这些标准和流程做出来，形成文档，落到知识库中。 如果只做到这一步，这些标准和流程可能就真的只是一个文档，情况好一点，有人来推进重视，可能会落实一些，但一旦这个推进人不在了，或者不再关注了，很多形式就不了了之。&lt;/p&gt;
 &lt;p&gt;要解决这个问题只能通过工具或系统，以工具或系统的形式固化标准和流程，把这此好的经验和方式以更物理的方式沉淀下来。再以这些工具或系统为杠杆，提升整体研发的效率，创造增量的价值。&lt;/p&gt;
 &lt;h1&gt;4. 系统&lt;/h1&gt;
 &lt;p&gt;这里的系统不仅仅是指使用某个系统，在使用系统的基础上整合，实现我们高效执行的目的。 前面我们有了机制，但是事情太多，不能让所有的事情用人来去解决，需要用系统来解决。&lt;/p&gt;
 &lt;p&gt;机制解决规模化的问题，系统解决规模固化的问题。 系统解决的问题有两个层面，一个是过程跟进，一个是结果度量。&lt;/p&gt;
 &lt;h2&gt;4.1 工作流和过程跟进系统&lt;/h2&gt;
 &lt;p&gt;一个研发部门可以看作一个系统，需求从一端进入，经历各种正确的工序，才能变成产品，如期从另一边离开。 当系统内部存在冲突，或者不和，或者互相针对，那么就会发生各种想象不到的问题，从而让整个系统的产出变少，甚至没有。&lt;/p&gt;
 &lt;p&gt;着眼于整个工作流，确认瓶颈点在哪，尽可能的运用各种技术和流程来确保工作在计划内有效的执行。因为我们知道：在代码投产之前，实际上并未产生任何价值，因为那只是困在系统里的半成品。&lt;/p&gt;
 &lt;p&gt;在实际中我们如何让大家更好的协同，更好的让这个工作流运转起来呢？以前可能是 Excel、Word 或者 todolist，再加上邮件或 IM 传来传去，现在更进一步有在线的表格协同，还有更完整的项目管理系统，如 Jira 、Trello、腾讯的 TAPD、阿里的 Teambition、禅道等。工具不同，但是目标是相同的，都是希望做到对项目执行的管控、对团队事务（问题）的跟踪，对需要多人协作任务的快速流转和处理。&lt;/p&gt;
 &lt;p&gt;除此之外，还需要文档管理，即过程中的物料也需要跟进起来，关联起来。至于是一个系统，还是多个系统，都是可以的。&lt;/p&gt;
 &lt;p&gt;系统虽有，但用得怎么样不好说，一个好的系统用不起来也是白搭，这里作为管理者需要推动起来的。&lt;/p&gt;
 &lt;h2&gt;4.2 工作可视化&lt;/h2&gt;
 &lt;p&gt;项目管理的系统用起来后，我们的工作流转就会落到系统里面，此时根据系统的数据，我们可以让工作可视化，透明化，能够清晰的观察工作流动的情况，从而发现瓶颈。发现问题是最难的，很多时候我们不知道有什么问题，包括自己。发现问题，才能解决问题，方法总是有的。 在资源有限的情况下，对非约束点的改进看起来很正确，但实质上毫无帮助，甚至会消耗宝贵资源拖累真正需要解决的问题。&lt;/p&gt;
 &lt;p&gt;我们可以构建一些看板，看一个产品从产品到设计、到研发实现，到测试完成，上线发布，再到线上问题跟进等等的情况。看效率，看一个需求从出现想法到用户看到需要多少时间，每个环节需要多少时间，哪些需求在哪些环节停留太久？不同的需求，不同的人，不同的产品做多个层次的对比，从而发现问题解决问题，让一切都在阳光下进行。&lt;/p&gt;
 &lt;p&gt;同时，我们可以让系统和数据告诉我们，整体团队的投入如何，有多少同学的工作是可以追溯的，有多少人力是隐藏在不为人知的地方的，能看到我们的时间都去哪了。&lt;/p&gt;
 &lt;p&gt;基于这样的看板，我们从两个角度优化整个系统：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;从左到右的流动，看从产品、设计、研发到运维的工作情况。为了最大程度地优化工作流，需要将工作可视化，减小每批次大小和等待间隔，通过内建质量杜绝向下游传递缺陷，并持续地优化全局目标；&lt;/li&gt;
  &lt;li&gt;从右向左的反馈，每个阶段中，应用持续、快速的工作反馈机制。通过放大反馈环防止问题复发，并缩短问题发现时间，实现快速修复。通过这种方式，我们能从源头控制质量，并在流程中嵌入相关的知识。这样不仅能创造出更安全的工作系统，还可以在灾难性事故发生前就检测到并解决它。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;总体来说，先透明出来，再优化，打开黑盒，问题会简单很多。&lt;/p&gt;
 &lt;h2&gt;4.3 其它&lt;/h2&gt;
 &lt;p&gt;以上的两个是从任务跟进和结果度量的角度，或者说从项目管理的角度来看整个团队的运转。 换一个角度，从研发同学工作本身，有没有需要系统化的地方？&lt;/p&gt;
 &lt;p&gt;代码管理是否系统化，Code Review、接口文档、接口自动化测试、Mock 数据、测试数据集管理、用户数据自动脱敏重放，代码从写完提交到代码库之后到上线，线上巡查等等这些是否有系统化？&lt;/p&gt;
 &lt;p&gt;这并不是今天我们要讲的话题，但是就系统化来说，这些都是必不可少的关键点。不管是哪方面，我们的原则是尽量减少人工介入，把人的经验变成代码和系统。&lt;/p&gt;
 &lt;h1&gt;5. 后记&lt;/h1&gt;
 &lt;p&gt;这篇文章务虚居多，也比较散，但是确实是技术管理者日常工作中要不停思考的点。 思考这些是用来帮助厘清思路，并不具备实操性，也就是不能实际的解决问题。 不同的公司，不同团队，问题不同，解决的方法也不同，欢迎一起探讨。&lt;/p&gt;
 &lt;p&gt;打开「黑盒」，问题会简单很多。常思考人、组织、机制和系统，这 4 个方面，发现其中的问题，并厘清解决问题的思路，一步一步，有节奏的去解决。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;你好，我是潘锦，超过 10 年的研发管理和技术架构经历，出过书，创过业，带过百人团队，也在腾讯，A 股上市公司呆过一些年头，现在在一家 C 轮的公司负责一些技术方面的管理工作。早年做过 NOI 和 ACM，对前端架构、跨端、后端架构、云原生、DevOps 等技术始终保持着浓厚的兴趣，平时喜欢读书、思考，终身学习实践者，欢迎一起交流学习。微信公众号：架构和远方，博客：    &lt;a href="http://www.phppan.com/"&gt;www.phppan.com&lt;/a&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>架构和远方 团队管理 技术管理 组织建设</category>
      <guid isPermaLink="true">https://itindex.net/detail/62384-%E6%8A%80%E6%9C%AF-%E7%AE%A1%E7%90%86-%E6%80%9D%E8%80%83</guid>
      <pubDate>Sat, 27 Aug 2022 10:53:00 CST</pubDate>
    </item>
    <item>
      <title>Java 技术栈中间件优雅停机方案设计与实现全景图</title>
      <link>https://itindex.net/detail/62377-java-%E6%8A%80%E6%9C%AF-%E4%B8%AD%E9%97%B4%E4%BB%B6</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;本系列 Netty 源码解析文章基于     &lt;strong&gt;4.1.56.Final&lt;/strong&gt;版本&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;本文概要&lt;/h2&gt; &lt;p&gt;在上篇文章  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247485060&amp;idx=1&amp;sn=736360af6eb3a4db496de2d6665ebd3c&amp;chksm=ce77c0c3f90049d5e44692c2cf837e8d85bb28758b243d505c43c48ce703da1edadfc19360b1&amp;scene=21#wechat_redirect"&gt;我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭，异常关闭，半关闭场景&lt;/a&gt;中笔者为大家详细介绍了 Netty 在处理连接关闭时的完整过程，并详细介绍了 Netty 如何应对 TCP 连接在关闭时会遇到的各种场景。&lt;/p&gt; &lt;p&gt;在连接关闭之后，接下来就轮到 Netty 的谢幕时刻了，本文笔者会为大家详尽 Java 技术栈中间件中关于优雅停机方案的详细设计和实现。&lt;/p&gt; &lt;p&gt;笔者会从日常开发工作中常见的版本发布，服务上下线的场景聊起，引出服务优雅启停的需求，并从这个需求出发，一步一步带大家探究各个中间件里的优雅停机的相关设计。&lt;/p&gt; &lt;p&gt;熟悉笔者文风的读者朋友应该知道，笔者肯定不会只是简单的介绍，要么不讲，要讲就要把整个技术体系的前世今生给大家讲清楚，讲明白。&lt;/p&gt; &lt;p&gt;基于这目的，笔者会先从支持优雅停机的底层技术基石--内核中的信号量开始聊起。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;image.png &lt;p&gt;从内核层我们接着会聊到 JVM 层，在 JVM 层一探优雅停机底层的技术玄机。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;image.png &lt;p&gt;随后我们会从 JVM 层一路奔袭到 Spring 然后到 Dubbo。在这个过程中，笔者还会带大家一起 Shooting Dubbo 在优雅停机下的一个 Bug，并为大家详细介绍修复过程。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;image.png &lt;p&gt;最后由 Dubbo 层的优雅停机，引出我们的主角--Netty 优雅停机的设计与实现：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Reactor优雅关闭总流程.png &lt;p&gt;下面我们来正式开始本文的内容~~&lt;/p&gt; &lt;img&gt;&lt;/img&gt;本文概要.png &lt;h2&gt;1. Java 进程的优雅启停&lt;/h2&gt; &lt;p&gt;在我们的日常开发工作中，业务需求的迭代和优化伴随围绕着我们整个开发周期，当我们加班加点完成了业务需求的开发，然后又历经各种艰难险阻通过了测试的验证，最后经过和产品经理的各种纠缠相爱相杀之后，终于到了最最激动人心的时刻程序要部署上线了。&lt;/p&gt; &lt;p&gt;               &lt;img&gt;&lt;/img&gt;&lt;/p&gt;上线时的情绪波动.png &lt;p&gt;那么在程序部署上线的过程中势必会涉及到线上服务的关闭和重启，关于对线上服务的启停这里面有很多的讲究，万万不能简单粗暴的进行关闭和重启，因为此时线上服务可能承载着生产的流量，可能正在进行重要的业务处理流程。&lt;/p&gt; &lt;p&gt;比如：用户正在购买商品，钱已经付了，恰好这时赶上程序上线，如果我们这时简单粗暴的对服务进行关闭，重启，可能就会导致用户付了钱，但是订单未创建或者商品未出现在用户的购物清单中，给用户造成了实质的损失，这是非常严重的后果。&lt;/p&gt; &lt;p&gt;为了保证能在程序上线的过程中做到业务无损，所以线上服务的  &lt;code&gt;优雅关闭&lt;/code&gt;和  &lt;code&gt;优雅启动&lt;/code&gt;显得就非常非常重要了。&lt;/p&gt; &lt;p&gt;                                 &lt;img&gt;&lt;/img&gt;&lt;/p&gt;保持优雅很重要.png &lt;h3&gt;1.1 优雅启动&lt;/h3&gt; &lt;p&gt;在 Java 程序的运行过程中，程序的运行速度一般会随着程序的运行慢慢的提高，所以从线上表现上来看 Java 程序在运行一段时间后往往会比程序刚启动的时候会快很多。&lt;/p&gt; &lt;p&gt;这是因为 Java 程序在运行过程中，JVM 会不断收集到程序运行时的动态数据，这样可以将高频执行代码通过即时编译成机器码，随后程序运行就直接执行机器码，运行速度完全不输 C 或者 C++ 程序。&lt;/p&gt; &lt;p&gt;同时在程序执行过程中，用到的类会被加载到 JVM 中缓存，这样当程序再次使用到的时候不会触发临时加载，影响程序执行性能。&lt;/p&gt; &lt;p&gt;我们可以将以上几点当做 JVM 带给我们的性能红利，  &lt;strong&gt;而当应用程序重新启动之后，这些性能红利也就消失了&lt;/strong&gt;，如果我们让新启动的程序继续承担之前的流量规模，那么就会导致程序在刚启动的时候在没有这些性能红利的加持下直接进入高负荷的运转状态，这就可能导致线上请求大面积超时，对业务造成影响。&lt;/p&gt; &lt;p&gt;所以说优雅地启动一个程序是非常重要的，优雅启动的核心思想就是让程序在刚启动的时候不要承担太大的流量，让程序在低负荷的状态下运行一段时间，使其提升到最佳的运行状态时，在逐步的让程序承担更大的流量处理。&lt;/p&gt; &lt;p&gt;下面我们就来看下常用于优雅启动场景的两个技术方案：&lt;/p&gt; &lt;h4&gt;1.1.1 启动预热&lt;/h4&gt; &lt;p&gt;启动预热就是让刚刚上线的应用程序不要一下就承担之前的全部流量，而是在一个时间窗口内慢慢的将流量打到刚上线的应用程序上，目的是让 JVM 先缓慢的收集程序运行时的一些动态数据，将高频代码即时编译为机器码。&lt;/p&gt; &lt;p&gt;这个技术方案在众多 RPC 框架的实现中我们都可以看到，服务调用方会从注册中心拿到所有服务提供方的地址，然后从这些地址中通过特定的负载均衡算法从中选取一个服务提供方的发送请求。&lt;/p&gt; &lt;p&gt;为了能够使刚刚上线的服务提供方有时间去预热，所以我们就要从源头上控制服务调用方发送的流量，服务调用方在发起 RPC 调用时应该尽量少的去负载均衡到刚刚启动的服务提供方实例。&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;h4&gt;1.1.2 延迟暴露&lt;/h4&gt; &lt;p&gt;启动预热更多的是从服务调用方的角度通过降低刚刚启动的服务提供方实例的负载均衡权重来实现优雅启动。&lt;/p&gt; &lt;p&gt;而延迟暴露则是从服务提供方的角度，延迟暴露服务时间，利用延迟的这段时间，服务提供方可以预先加载依赖的一些资源，比如：缓存数据，spring 容器中的 bean 。等到这些资源全部加载完毕就位之后，我们在将服务提供方实例暴露出去。这样可以有效降低启动前期请求处理出错的概率。&lt;/p&gt; &lt;p&gt;比如我们可以在 dubbo 应用中可以配置服务的延迟暴露时间：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;//延迟5秒暴露服务   &lt;br /&gt;&amp;lt;dubbo:service delay=&amp;quot;5000&amp;quot; /&amp;gt;    &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;1.2 优雅关闭&lt;/h3&gt; &lt;p&gt;优雅关闭需要考虑的问题和处理的场景要比优雅启动要复杂的多，因为一个正常在线上运行的服务程序正在承担着生产的流量，同时也正在进行业务流程的处理。&lt;/p&gt; &lt;p&gt;要对这样的一个服务程序进行优雅关闭保证业务无损还是非常有挑战的，一个好的关闭流程，可以确保我们业务实现平滑的上下线，避免上线之后增加很多不必要的额外运维工作。&lt;/p&gt; &lt;p&gt;下面我们就来讨论下具体应该从哪几个角度着手考虑实现优雅关闭：&lt;/p&gt; &lt;h4&gt;1.2.1 切走流量&lt;/h4&gt; &lt;p&gt;                                          &lt;img&gt;&lt;/img&gt;&lt;/p&gt;image.png &lt;p&gt;第一步肯定是要将程序承担的现有流量全部切走，告诉服务调用方，我要进行关闭了，请不要在给我发送请求。那么如果进行切流呢？？&lt;/p&gt; &lt;p&gt;在 RPC 的场景中，服务调用方通过服务发现的方式从注册中心中动态感知服务提供者的上下线变化。在服务提供方关闭之前，首先就要自己从注册中心中取消注册，随后注册中心会通知服务调用方，有服务提供者实例下线，请将其从本地缓存列表中剔除。这样就可以使得服务调用方之后的 RPC 调用不在请求到下线的服务提供方实例上。&lt;/p&gt; &lt;p&gt;但是这里会有一个问题，就是通常我们的注册中心都是 AP 类型的，它只会保证最终一致性，并不会保证实时一致性，基于这个原因，服务调用方感知到服务提供者下线的事件可能是延后的，那么在这个延迟时间内，服务调用方极有可能会向正在下线的服务发起 RPC 请求。&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;strong&gt;被动通知&lt;/strong&gt;的两个方案结合在一起应该就能确保万无一失了吧。&lt;/p&gt; &lt;p&gt;事实上，在大部分场景下这个方案是可行的，但是还有一种极端的情况需要应对，就是当服务提供方通知调用方自己下线的网络请求在到达服务调用方之前的很极限的一个时间内，服务调用者向正在下线的服务提供方发起了 RPC 请求，这种极端的情况，就需要服务提供方和调用方一起配合来应对了。&lt;/p&gt; &lt;p&gt;首先服务提供方在准备关闭的时候，就把自己设置为正在关闭状态，在这个状态下不会接受任何请求，如果这时遇到了上边这种极端情况下的请求，那么就抛出一个 CloseException （这个异常是提供方和调用方提前约定好的），调用方收到这个 CloseException ，则将该服务提供方的节点剔除，并从剩余节点中通过负载均衡选取一个节点进行重试，通过让这个请求快速失败从而保证业务无损。&lt;/p&gt; &lt;p&gt;这三种方案结合在一起，笔者认为就是一个比较完美的切流方案了。&lt;/p&gt; &lt;h4&gt;1.2.2 尽量保证业务无损&lt;/h4&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;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;我们首先要做的就是将当前将要关闭的应用程序上承载的生产流量全部切走，保证不会有新的流量打到将要关闭的应用程序实例上。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;当所有的生产流量切走之后，我们还需要保证当前将要关闭的应用程序实例正在处理的业务请求要使其处理完毕，并将业务处理结果响应给客户端。以保证业务无损。当然为了使关闭流程变得可控，我们需要引入关闭超时时间。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;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;2. 内核信号机制&lt;/h2&gt; &lt;p&gt;信号是操作系统内核为我们提供用于在进程间通信的机制，内核可以利用信号来通知进程，当前系统所发生的的事件（包括关闭进程事件）。&lt;/p&gt; &lt;p&gt;信号在内核中并没有用特别复杂的数据结构来表示，只是用一个代号一样的数字来标识不同的信号。Linux 提供了几十种信号，分别代表不同的意义。信号之间依靠它们的值来区分&lt;/p&gt; &lt;p&gt;信号可以在任何时候发送给进程，进程需要为这个信号配置信号处理函数。当某个信号发生的时候，就默认执行对应的信号处理函数就可以了。这就相当于一个操作系统的应急手册，事先定义好遇到什么情况，做什么事情，提前准备好，出了事情照着做就可以了。&lt;/p&gt; &lt;p&gt;内核发出的信号就代表当前系统遇到了某种情况，我们需要应对的步骤就封装在对应信号的回调函数中。&lt;/p&gt; &lt;p&gt;信号机制引入的目的就在于：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;让应用进程知道当前已经发生了某个特定的事件（比如进程的关闭事件）。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;强制进程执行我们事先设定好的信号处理函数（比如封装优雅关闭逻辑）。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;通常来说程序一旦启动就会一直运行下去，除非遇到 OOM 或者我们需要重新发布程序时会在运维脚本中调用 kill 命令关闭程序。Kill 命令从字面意思上来说是杀死进程，但是其本质是向进程发送信号，从而关闭进程。&lt;/p&gt; &lt;p&gt;下面我们使用 kill -l 命令查看下 kill 命令可以向进程发送哪些信号：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;# kill -l   &lt;br /&gt;1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP   &lt;br /&gt;6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1   &lt;br /&gt;11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM   &lt;br /&gt;16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP   &lt;br /&gt;21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ   &lt;br /&gt;26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR   &lt;br /&gt;31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3   &lt;br /&gt;38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8   &lt;br /&gt;43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13   &lt;br /&gt;48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12   &lt;br /&gt;53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7   &lt;br /&gt;58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2   &lt;br /&gt;63) SIGRTMAX-1  64) SIGRTMAX&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;笔者这里提取几个常见的信号来简要说明下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;SIGINT：&lt;/code&gt;信号代号为 2 。比如我们在终端以非后台模式运行一个进程实例时，要想关闭它，我们可以通过 Ctrl+C 来关闭这个前台程序。这个 Ctrl+C 向进程发送的正是 SIGINT 信号。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;SIGQUIT：&lt;/code&gt;信号代号为 3 。比如我们使用 Ctrl+\ 来关闭一个前台进程，此时会向进程发送 SIGQUIT 信号，    &lt;strong&gt;与 SIGINT 信号不同的是&lt;/strong&gt;，通过 SIGQUIT 信号终止的进程会在退出时，通过 Core Dump 将当前进程的运行状态保存在 core dump 文件里面，方便后续查看。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;SIGKILL：&lt;/code&gt;信号代号为 9 。通过 kill -9 pid 命令结束进程是非常非常危险的动作，    &lt;strong&gt;我们应该坚决制止这种关闭进程的行为&lt;/strong&gt;，因为 SIGKILL 信号是不能被进程捕获和忽略的，只能执行内核定义的默认操作直接关闭进程。    &lt;strong&gt;而我们的优雅关闭操作是需要通过捕获操作系统信号，从而可以在对应的信号处理函数中执行优雅关闭的动作&lt;/strong&gt;。由于 SIGKILL 信号不能被捕获，所以优雅关闭也就无法实现。现在大家就赶快检查下自己公司生产环境的运维脚本是否是通过 kill -9 pid 命令来结束进程的，一定要避免用这种方式，因为这种方式是极其无情并且略带残忍的关闭进程行为。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;           &lt;img&gt;&lt;/img&gt;&lt;/p&gt;image.png &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;SIGSTOP ：&lt;/code&gt;信号代号为 19 。该信号和 SIGKILL 信号一样都是无法被应用程序忽略和捕获的。向进程发送 SIGSTOP 信号也是无法实现优雅关闭的。通过 Ctrl+Z 来关闭一个前台进程，发送的信号就是 SIGSTOP 信号。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;SIGTERM：&lt;/code&gt;信号代号为 15 。我们通常会使用 kill 命令来关闭一个后台运行的进程，kill 命令发送的默认信号就是 SIGTERM ，    &lt;strong&gt;该信号也是本文要讨论的优雅关闭的基础&lt;/strong&gt;，我们通常会使用 kill pid 或者 kill -15 pid 来向后台进程发送 SIGTERM 信号用以实现进程的优雅关闭。大家如果发现自己公司生产环境的运维脚本中使用的是 kill -9 pid 命令来结束进程，那么就要马上换成 kill pid 命令。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;以上列举的都是我们常用的一些信号，大家也可以通过 man 7 signal 命令查看每种信号对应的含义：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;Signal     Value     Action   Comment   &lt;br /&gt;──────────────────────────────────────────────────────────────────────   &lt;br /&gt;SIGHUP        1       Term    Hangup detected on controlling terminal   &lt;br /&gt;or death of controlling process   &lt;br /&gt;SIGINT        2       Term    Interrupt from keyboard   &lt;br /&gt;SIGQUIT       3       Core    Quit from keyboard   &lt;br /&gt;SIGILL        4       Core    Illegal Instruction   &lt;br /&gt;   &lt;br /&gt;   &lt;br /&gt;SIGABRT       6       Core    Abort signal from abort(3)   &lt;br /&gt;SIGFPE        8       Core    Floating point exception   &lt;br /&gt;SIGKILL       9       Term    Kill signal   &lt;br /&gt;SIGSEGV      11       Core    Invalid memory reference   &lt;br /&gt;SIGPIPE      13       Term    Broken pipe: write to pipe with no   &lt;br /&gt;readers   &lt;br /&gt;SIGALRM      14       Term    Timer signal from alarm(2)   &lt;br /&gt;SIGTERM      15       Term    Termination signal   &lt;br /&gt;SIGUSR1   30,10,16    Term    User-defined signal 1   &lt;br /&gt;SIGUSR2   31,12,17    Term    User-defined signal 2   &lt;br /&gt;……&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而应用进程对于信号的处理一般分为以下三种方式：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;内核定义的默认操作：&lt;/code&gt;系统内核对每种信号都规定了默认操作，比如上面列表 Action 列中的 Term ，就是终止进程的意思。前边介绍的 SIGINT 信号和 SIGTERM 信号的默认操作就是 Term 。Core 的意思是 Core Dump ，即终止进程后会通过 Core Dump 将当前进程的运行状态保存在文件里面，方便我们事后进行分析问题在哪里。前边介绍的 SIGQUIT 信号默认操作就是 Core 。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;捕获信号：&lt;/code&gt;应用程序可以利用内核提供的系统调用来捕获信号，并将优雅关闭的步骤封装在对应信号的处理函数中。当向进程发送关闭信号 SIGTERM  的时候，在进程内我们可以通过捕获 SIGTERM 信号，随即就会执行我们自定义的信号处理函数。我们从而可以在信号处理函数中执行进程优雅关闭的逻辑。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;忽略信号：&lt;/code&gt;当我们不希望处理某些信号的时候，就可以忽略该信号，不做任何处理，    &lt;strong&gt;但是前边介绍的 SIGKILL 信号和 SIGSTOP 是无法被捕获和忽略的，内核会直接执行这两个信号定义的默认操作直接关闭进程。&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;当我们不希望信号执行内核定义的默认操作时，我们就需要在进程内捕获信号，并注册信号的回调函数来执行我们自定义的信号处理逻辑。&lt;/p&gt; &lt;p&gt;比如我们在本文中要讨论的优雅关闭场景，当进程接收到 SIGTERM 信号时，为了实现进程的优雅关闭，我们并不希望进程执行 SIGTERM 信号的默认操作直接关闭进程，所以我们要在进程中捕获 SIGTERM 信号，并将优雅关闭的操作步骤封装在对应的信号处理函数中。&lt;/p&gt; &lt;h3&gt;2.1 如何捕获信号&lt;/h3&gt; &lt;p&gt;在介绍完了内核信号的分类以及进程对于信号处理的三种方式之后，下面我们来看下如何来捕获内核信号，并在对应信号回调函数中自定义我们的处理逻辑。&lt;/p&gt; &lt;p&gt;内核提供了 sigaction 系统调用，来供我们捕获信号以及与相应的信号处理函数绑定起来。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;int sigaction(int signum, const struct sigaction *act,   &lt;br /&gt;                     struct sigaction *oldact);   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;int signum：&lt;/code&gt;表示我们想要在进程中捕获的信号，比如本文中我们要实现优雅关闭就需要在进程中捕获 SIGTERM 信号，对应的 signum = 15 。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;struct sigaction *act：&lt;/code&gt;内核中会用一个 sigaction 结构体来封装我们自定义的信号处理逻辑。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;struct sigaction *oldact：&lt;/code&gt;这里是为了兼容老的信号处理函数，了解一下就可以了，和本文主线无关。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;sigaction 结构体用来封装信号对应的处理函数，以及更加精细化控制信号处理的信息。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;struct sigaction {   &lt;br /&gt;  __sighandler_t sa_handler;   &lt;br /&gt;  unsigned long sa_flags;   &lt;br /&gt;        .......   &lt;br /&gt;  sigset_t sa_mask;    &lt;br /&gt;};   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;__sighandler_t sa_handler：&lt;/code&gt;其实本质上是一个函数指针，用来保存我们为信号注册的信号处理函数，    &lt;strong&gt;优雅关闭的逻辑就封装在这里&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;long sa_flags：&lt;/code&gt;为了更加精细化的控制信号处理逻辑，这个字段保存了一些控制信号处理行为的选项集合。常见的选项有：&lt;/p&gt;&lt;/li&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;SA_ONESHOT&lt;/strong&gt;：意思是我们注册的信号处理函数，仅仅只起一次作用。响应完一次后，就设置回默认行为。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;SA_NOMASK&lt;/strong&gt;：表示信号处理函数在执行的过程中会被中断。比如我们进程捕获到一个感兴趣的信号，随后会执行注册的信号处理函数，但是此时进程又收到其他的信号或者和上次相同的信号，此时正在执行的信号处理函数会被中断，从而转去执行最新到来的信号处理函数。     &lt;strong&gt;如果连续产生多个相同的信号，那么我们的信号处理函数就要做好同步，幂等等措施&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;SA_INTERRUPT&lt;/strong&gt;：当进程正在执行一个非常耗时的系统调用时，如果此时进程接收到了信号，那么这个系统调用将会被信号中断，进程转去执行相应的信号处理函数。那么当信号处理函数执行完时，如果这里设置了 SA_INTERRUPT ，那么系统调用将不会继续执行并且会返回一个     &lt;code&gt;-EINTR&lt;/code&gt;常量，告诉调用方，这个系统调用被信号中断了，怎么处理你看着办吧。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;SA_RESTART&lt;/strong&gt;：当系统调用被信号中断后，相应的信号处理函数执行完毕后，如果这里设置了 SA_RESTART 系统调用将会被自动重新启动。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;sigset_t sa_mask：&lt;/code&gt;这个字段主要指定在信号处理函数正在运行的过程中，如果连续产生多个信号，需要屏蔽哪些信号。也就是说当进程收到屏蔽的信号时，正在进行的信号处理函数不会被中断。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;屏蔽并不意味着信号一定丢失，而是暂存，这样可以使相同信号的处理函数，在进程连续接收到多个相同的信号时，可以一个一个的处理。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;最终通过 sigaction 函数会调用到底层的系统调用 rt_sigaction 函数，在
rt_sigaction 中会将上边介绍的用户态 struct sigaction 结构拷贝为内核态的
k_sigaction ，然后调用 do_sigaction 函数。&lt;/p&gt; &lt;p&gt;最后在 do_sigaction 函数中将用户要在进程中捕获的信号以及相应的信号处理函数设置到进程描述符 task_struct 结构里。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;进程中的信号结构.png &lt;p&gt;进程在内核中的数据结构 task_struct 中有一个 struct sighand_struct 结构的属性 sighand ，struct sighand_struct 结构中包含一个 k_sigaction 类型的数组 action[] ，这个数组保存的就是进程中需要捕获的信号以及对应的信号处理函数在内核中的结构体 k_sigaction ，数组下标为进程需要捕获的信号。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;#include &amp;lt;signal.h&amp;gt;   &lt;br /&gt;   &lt;br /&gt;static void sig_handler(int signum) {   &lt;br /&gt;   &lt;br /&gt;    if (signum == SIGTERM) {   &lt;br /&gt;   &lt;br /&gt;        .....执行优雅关闭逻辑....   &lt;br /&gt;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;int main (Void) {   &lt;br /&gt;   &lt;br /&gt;    struct sigaction sa_usr; //定义sigaction结构体   &lt;br /&gt;    sa_usr.sa_flags = 0;   &lt;br /&gt;    sa_usr.sa_handler = sig_handler;   //设置信号处理函数   &lt;br /&gt;   &lt;br /&gt;    sigaction(SIGTERM, &amp;amp;sa_usr, NULL);//进程捕获信号，注册信号处理函数   &lt;br /&gt;           &lt;br /&gt;        ,,,,,,,,,,,,   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们可以通过如上简单的示例代码，将 SIGTERM 信号及其对应的自定义信号处理函数注册到进程中，当我们执行 kill -15 pid 命令之后，进程就会捕获到 SIGTERM 信号，随后就可以执行优雅关闭步骤了。&lt;/p&gt; &lt;h2&gt;3. JVM 中的 ShutdownHook&lt;/h2&gt; &lt;p&gt;在《2. 内核信号机制》小节中为大家介绍的内容是操作系统内核为我们实现进程的优雅关闭提供的最底层系统级别的支持机制，在内核的强力支持下，那么本文的主题 Java 进程的优雅关闭就很容易实现了。&lt;/p&gt; &lt;p&gt;我们要想实现 Java 进程的优雅关闭功能，只需要在进程启动的时候将优雅关闭的操作封装在一个 Thread 中，随后将这个 Thread 注册到 JVM 的 ShutdownHook 中就好了，当 JVM 进程接收到 kill -15 信号时，就会执行我们注册的 ShutdownHook 关闭钩子，进而执行我们定义的优雅关闭步骤。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;        Runtime.getRuntime().addShutdownHook(new Thread(){   &lt;br /&gt;            @Override   &lt;br /&gt;            public void run() {   &lt;br /&gt;                .....执行优雅关闭步骤.....   &lt;br /&gt;            }   &lt;br /&gt;        });   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;3.1 导致 JVM 退出的几种情况&lt;/h3&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;JVM 进程中最后一个非守护线程退出。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;在程序代码中主动调用 java.lang.System#exit(int status) 方法，会导致 JVM 进程的退出并触发 ShutdownHook 的调用。参数 int status 如果是非零值，则表示本次关闭是在一个非正常情况下的关闭行为。比如：进程发生 OOM 异常或者其他运行时异常。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;pre&gt;  &lt;code&gt;public static void main(String[] args) {   &lt;br /&gt;        try {   &lt;br /&gt;   &lt;br /&gt;           ......进程启动main函数.......   &lt;br /&gt;   &lt;br /&gt;        } catch (RuntimeException e) {   &lt;br /&gt;            logger.error(e.getMessage(), e);   &lt;br /&gt;            // JVM 进程主动关闭触发调用 shutdownHook   &lt;br /&gt;            System.exit(1);   &lt;br /&gt;        }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt;  &lt;li&gt;   &lt;p&gt;当 JVM 进程接收到第二小节《2.内核信号机制》介绍的那些关闭信号时， JVM 进程会被关闭。    &lt;strong&gt;由于 SIGKILL 信号和 SIGSTOP 信号不能够被进程捕获和忽略&lt;/strong&gt;，这两个信号会直接粗暴地关闭 JVM 进程，所以一般我们会发送 SIGTERM 信号，JVM 进程通过捕获 SIGTERM 信号，从而可以执行我们定义的 ShutdownHook 完成优雅关闭的操作。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Native Method 执行过程中发生错误，比如试图访问一个不存在的内存，这样也会导致 JVM 强制关闭，ShutdownHook 也不会运行。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;3.2 使用 ShutdownHook 的注意事项&lt;/h3&gt; &lt;ol&gt;  &lt;li&gt;ShutdownHook 其实本质上是一个已经被初始化但是未启动的 Thread ，这些通过 Runtime.getRuntime().addShutdownHook 方法注册的 ShutdownHooks ，在 JVM 进程关闭的时候会被启动   &lt;strong&gt;并发执行&lt;/strong&gt;，但是并   &lt;strong&gt;不会保证执行顺序&lt;/strong&gt;。&lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt;  &lt;p&gt;所以在编写 ShutdownHook 中的逻辑时，我们应该确保程序的线程安全性，并尽可能避免死锁。最好是一个 JVM 进程只注册一个 ShutdownHook 。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="2"&gt;  &lt;li&gt;如果我们通过   &lt;code&gt;java.lang.Runtime#runFinalizersOnExit(boolean value)&lt;/code&gt;开启了 finalization-on-exit ，那么当所有 ShutdownHook 运行完毕之后，JVM 在关闭之前将会继续调用所有未被调用的 finalizers 方法。默认 finalization-on-exit 选项是关闭的。&lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt;  &lt;p&gt;   &lt;strong&gt;注意&lt;/strong&gt;：当 JVM 开始关闭并执行上述关闭操作的时候，守护线程是会继续运行的，如果用户使用 java.lang.System#exit(int status) 方法主动发起 JVM 关闭，那么关闭期间非守护线程也是会继续运行的。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="3"&gt;  &lt;li&gt;一旦 JVM 进程开始关闭，一般情况下这个过程是不可以被中断的，除非操作系统强制中断或者用户通过调用 java.lang.Runtime#halt(int status) 来强制关闭。&lt;/li&gt;&lt;/ol&gt; &lt;pre&gt;  &lt;code&gt;   public void halt(int status) {   &lt;br /&gt;        SecurityManager sm = System.getSecurityManager();   &lt;br /&gt;        if (sm != null) {   &lt;br /&gt;            sm.checkExit(status);   &lt;br /&gt;        }   &lt;br /&gt;        Shutdown.halt(status);   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt;  &lt;p&gt;java.lang.Runtime#halt(int status) 方法是用来强制关闭正在运行的 JVM 进程的，它会导致我们注册的 ShutdownHook 不会被运行和执行，如果此时 JVM 正在执行 ShutdownHook ，当调用该方法后，JVM 进程将会被强制关闭，并不会等待 ShutdownHook 执行完毕。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="4"&gt;  &lt;li&gt;   &lt;p&gt;当 JVM 关闭流程开始的时候，就不能在向其注册 ShutdownHook 或者取消注册之前已经注册好的 ShutdownHook 了，否则将会抛出 IllegalStateException异常。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;ShutdownHook 中的程序应该尽快的完成优雅关闭逻辑，因为当用户调用 System#exit 方法的时候是希望 JVM 在保证业务无损的情况下尽快完成关闭动作。这里并不适合做一些需要长时间运行的任务或者和用户交互的操作。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt;  &lt;p&gt;如果是因为物理机关闭从而导致的 JVM 关闭，那么操作系统只会允许 JVM 限定的时间内尽快的关闭，超过限定时间操作系统将会强制关闭 JVM 。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="6"&gt;  &lt;li&gt;ShutdownHook 中可能也会抛出异常，而 ShutdownHook 对于 JVM 来说本质上是一个 Thread ，那么对于 ShutdownHook 中未捕获的异常，JVM 的处理方法和其他普通的线程一样，都是通过调用 ThreadGroup#uncaughtException 方法来处理。此方法的默认实现是将异常的堆栈跟踪打印到 System#err 并终止异常的 ShutdownHook 线程。&lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt;  &lt;p&gt;   &lt;strong&gt;注意：&lt;/strong&gt;这里只会停止异常的 ShutdownHook ，但不会影响其他 ShutdownHook 线程的执行更不会导致 JVM 退出。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="7"&gt;  &lt;li&gt;   &lt;strong&gt;最后也是非常重要的一点是&lt;/strong&gt;，当 JVM 进程接收到 SIGKILL 信号和 SIGSTOP 信号时，是会强制关闭，并不会执行 ShutdownHook 。另外一种导致 JVM 强制关闭的情况就是 Native Method 执行过程中发生错误，比如试图访问一个不存在的内存，这样也会导致 JVM 强制关闭，ShutdownHook 也不会运行。&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;3.3 ShutdownHook 执行原理&lt;/h3&gt; &lt;p&gt;我们在 JVM 中通过 Runtime.getRuntime().addShutdownHook 添加关闭钩子，当 JVM 接收到 SIGTERM 信号之后，就会调用我们注册的这些 ShutdownHooks 。&lt;/p&gt; &lt;p&gt;本小节介绍的 ShutdownHook 就类似于我们在第二小节《内核信号机制》中介绍的信号处理函数。&lt;/p&gt; &lt;p&gt;大家这里一定会有个疑问，那就是在介绍内核信号机制小节中，我们可以通过系统调用 sigaction 函数向内核注册进程要捕获的信号以及对应的信号处理函数。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;int sigaction(int signum, const struct sigaction *act,   &lt;br /&gt;                     struct sigaction *oldact);   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是在本小节介绍的 JVM 中，我们只是通过 Runtime.getRuntime().addShutdownHook 注册了一个关闭钩子。但是并未注册 JVM 进程所需要捕获的信号。那么 JVM 是怎么捕获关闭信号的呢？&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;        Runtime.getRuntime().addShutdownHook(new Thread(){   &lt;br /&gt;            @Override   &lt;br /&gt;            public void run() {   &lt;br /&gt;                .....执行优雅关闭步骤.....   &lt;br /&gt;            }   &lt;br /&gt;        });   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;事实上，JVM 捕获操作系统信号的部分在 JDK 中已经帮我们处理好了，在用户层我们并不需要关注捕获信号的处理，只需要关注信号的处理逻辑即可。&lt;/p&gt; &lt;p&gt;下面我们就来看一下 JDK 是如何帮助我们将要捕获的信号向内核注册的？&lt;/p&gt; &lt;p&gt;当 JVM 第一个线程被初始化之后，随后就会调用 System#initializeSystemClass 函数来初始化 JDK 中的一些系统类，其中就包括注册 JVM 进程需要捕获的信号以及信号处理函数。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public final class System {   &lt;br /&gt;   &lt;br /&gt;    private static void initializeSystemClass() {   &lt;br /&gt;   &lt;br /&gt;           .......省略.......   &lt;br /&gt;   &lt;br /&gt;            // Setup Java signal handlers for HUP, TERM, and INT (where available).   &lt;br /&gt;           Terminator.setup();   &lt;br /&gt;   &lt;br /&gt;           .......省略.......   &lt;br /&gt;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从这里可以看出，JDK 在向 JVM 注册需要捕获的内核信号是在 Terminator 类中进行的。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;class Terminator {   &lt;br /&gt;    //信号处理函数   &lt;br /&gt;    private static SignalHandler handler = null;   &lt;br /&gt;   &lt;br /&gt;    static void setup() {   &lt;br /&gt;        if (handler != null) return;   &lt;br /&gt;        SignalHandler sh = new SignalHandler() {   &lt;br /&gt;            public void handle(Signal sig) {   &lt;br /&gt;                Shutdown.exit(sig.getNumber() + 0200);   &lt;br /&gt;            }   &lt;br /&gt;        };   &lt;br /&gt;        handler = sh;   &lt;br /&gt;   &lt;br /&gt;        try {   &lt;br /&gt;            Signal.handle(new Signal(&amp;quot;HUP&amp;quot;), sh);   &lt;br /&gt;        } catch (IllegalArgumentException e) {   &lt;br /&gt;        }   &lt;br /&gt;        try {   &lt;br /&gt;            Signal.handle(new Signal(&amp;quot;INT&amp;quot;), sh);   &lt;br /&gt;        } catch (IllegalArgumentException e) {   &lt;br /&gt;        }   &lt;br /&gt;        try {   &lt;br /&gt;            Signal.handle(new Signal(&amp;quot;TERM&amp;quot;), sh);   &lt;br /&gt;        } catch (IllegalArgumentException e) {   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;JDK 向我们提供了  &lt;code&gt;sun.misc.Signal#handle(Signal signal, SignalHandler signalHandler)&lt;/code&gt;函数来实现在 JVM 进程中对内核信号的捕获。底层依赖于我们在第二小节介绍的系统调用 sigaction 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;int sigaction(int signum, const struct sigaction *act,   &lt;br /&gt;                     struct sigaction *oldact);   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;code&gt;sun.misc.Signal#handle&lt;/code&gt;函数的参数含义和系统调用函数  &lt;code&gt;sigaction&lt;/code&gt;中的参数含义是一一对应的：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;Signal signal&lt;/code&gt;：表示要捕获的内核信号。从这里我们可以看出 JVM 主要捕获三种信号：SIGHUP(1)，SIGINT(2)，SIGTERM(15)。&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;除了上述的这三种信号之外，JVM 如果接收到其他信号，会执行系统内核默认的操作，直接关闭进程，并不会触发 ShutdownHook 的执行。&lt;/p&gt;&lt;/blockquote&gt; &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;SignalHandler handler&lt;/code&gt;：信号响应函数。我们看到这里直接调用了 Shutdown#exit 函数。&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;    SignalHandler sh = new SignalHandler() {   &lt;br /&gt;            public void handle(Signal sig) {   &lt;br /&gt;                Shutdown.exit(sig.getNumber() + 0200);   &lt;br /&gt;            }   &lt;br /&gt;        };   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们这里应该很容易就会猜测出 ShutdownHook 的调用应该就是在 Shutdown#exit 函数中被触发的。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;class Shutdown {   &lt;br /&gt;   &lt;br /&gt;    static void exit(int status) {   &lt;br /&gt;   &lt;br /&gt;          ........省略.........   &lt;br /&gt;   &lt;br /&gt;          synchronized (Shutdown.class) {   &lt;br /&gt;              // 开始 JVM 关闭流程，执行 ShutdownHooks   &lt;br /&gt;              sequence();   &lt;br /&gt;              // 强制关闭 JVM   &lt;br /&gt;              halt(status);   &lt;br /&gt;          }   &lt;br /&gt;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    private static void sequence() {   &lt;br /&gt;        synchronized (lock) {   &lt;br /&gt;            if (state != HOOKS) return;   &lt;br /&gt;        }   &lt;br /&gt;        //触发 ShutdownHooks   &lt;br /&gt;        runHooks();   &lt;br /&gt;        boolean rfoe;   &lt;br /&gt;        synchronized (lock) {   &lt;br /&gt;            state = FINALIZERS;   &lt;br /&gt;            rfoe = runFinalizersOnExit;   &lt;br /&gt;        }   &lt;br /&gt;        //如果 runFinalizersOnExit = true   &lt;br /&gt;        //开始运行所有未被调用过的 Finalizers   &lt;br /&gt;        if (rfoe) runAllFinalizers();   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Shutdown#sequence 函数中的逻辑就是我们在《3.2 使用ShutdownHook的注意事项》小节中介绍的 JVM 关闭时的运行逻辑：在这里会触发所有 ShutdownHook 的  &lt;strong&gt;并发运行&lt;/strong&gt;。注意这里并不会保证运行顺序。&lt;/p&gt; &lt;p&gt;当所有 ShutdownHook 运行完毕之后，如果我们通过  &lt;code&gt;java.lang.Runtime#runFinalizersOnExit(boolean value)&lt;/code&gt;开启了  &lt;code&gt;finalization-on-exit&lt;/code&gt;选项，JVM 在关闭之前将会继续调用所有未被调用的 finalizers 方法。默认 finalization-on-exit 选项是关闭的。&lt;/p&gt; &lt;h3&gt;3.4 ShutdownHook 的执行&lt;/h3&gt; &lt;img&gt;&lt;/img&gt;shutdownhook的运行.png &lt;p&gt;如上图所示，在 JDK 的 Shutdown 类中，包含了一个 Runnable[] hooks 数组，容量为 10 。JDK 中的 ShutdownHook 是以类型来分类的，数组 hooks 每一个槽中存放的是一种特定类型的 ShutdownHook 。&lt;/p&gt; &lt;p&gt;而我们通常在程序代码中通过 Runtime.getRuntime().addShutdownHook 注册的是  &lt;code&gt;Application hooks&lt;/code&gt;类型的 ShutdownHook ，存放在数组 hooks 中索引为 1 的槽中。&lt;/p&gt; &lt;p&gt;当在 Shutdown#sequence 中触发 runHooks() 函数开始运行 JVM 中所有类型的 ShutdownHooks 时，会在 runHooks() 函数中依次遍历数组 hooks 中的 Runnable ，进而开始运行 Runnable 中封装的 ShutdownHooks 。&lt;/p&gt; &lt;p&gt;当遍历到数组 Hooks 的第二个槽（索引为 1 ）的时候，  &lt;code&gt;Application hooks&lt;/code&gt;类型的 ShutdownHook 得以运行，也就是我们通过 Runtime.getRuntime().addShutdownHook 注册的 ShutdownHook 在这个时候开始运行起来。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;    // The system shutdown hooks are registered with a predefined slot.   &lt;br /&gt;    // The list of shutdown hooks is as follows:   &lt;br /&gt;    // (0) Console restore hook   &lt;br /&gt;    // (1) Application hooks   &lt;br /&gt;    // (2) DeleteOnExit hook   &lt;br /&gt;    private static final int MAX_SYSTEM_HOOKS = 10;   &lt;br /&gt;    private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];   &lt;br /&gt;   &lt;br /&gt;    /* Run all registered shutdown hooks   &lt;br /&gt;     */   &lt;br /&gt;    private static void runHooks() {   &lt;br /&gt;        for (int i=0; i &amp;lt; MAX_SYSTEM_HOOKS; i++) {   &lt;br /&gt;            try {   &lt;br /&gt;                Runnable hook;   &lt;br /&gt;                synchronized (lock) {   &lt;br /&gt;                    // acquire the lock to make sure the hook registered during   &lt;br /&gt;                    // shutdown is visible here.   &lt;br /&gt;                    currentRunningHook = i;   &lt;br /&gt;                    hook = hooks[i];   &lt;br /&gt;                }   &lt;br /&gt;                if (hook != null) hook.run();   &lt;br /&gt;            } catch(Throwable t) {   &lt;br /&gt;                if (t instanceof ThreadDeath) {   &lt;br /&gt;                    ThreadDeath td = (ThreadDeath)t;   &lt;br /&gt;                    throw td;   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;下面我们就来看一下，JDK 是如果通过 Runtime.getRuntime().addShutdownHook 函数将我们自定义的 ShutdownHook 注册到 Shutdown 类中的数组 Hooks 里的。&lt;/p&gt; &lt;h3&gt;3.5 ShutdownHook 的注册&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;public class Runtime {   &lt;br /&gt;   &lt;br /&gt;    public void addShutdownHook(Thread hook) {   &lt;br /&gt;        SecurityManager sm = System.getSecurityManager();   &lt;br /&gt;        if (sm != null) {   &lt;br /&gt;            sm.checkPermission(new RuntimePermission(&amp;quot;shutdownHooks&amp;quot;));   &lt;br /&gt;        }   &lt;br /&gt;        //注意 这里注册的是 Application 类型的 hooks   &lt;br /&gt;        ApplicationShutdownHooks.add(hook);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从 JDK 源码中我们看到在 Runtime 类中的 addShutdownHook 方法里，JDK 会将我们自定义的 ShutdownHook 封装在 ApplicationShutdownHooks 类中，从这类的命名上看，它里边封装的就是我们在上小节《3.4 ShutdownHook 的执行》提到的  &lt;code&gt;Application hooks&lt;/code&gt;类型的 ShutdownHook ，由用户自定义实现。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;class ApplicationShutdownHooks {   &lt;br /&gt;    // 存放用户自定义的 Application 类型的 hooks   &lt;br /&gt;    private static IdentityHashMap&amp;lt;Thread, Thread&amp;gt; hooks;   &lt;br /&gt;   &lt;br /&gt;    static synchronized void add(Thread hook) {   &lt;br /&gt;        if(hooks == null)   &lt;br /&gt;            throw new IllegalStateException(&amp;quot;Shutdown in progress&amp;quot;);   &lt;br /&gt;   &lt;br /&gt;        if (hook.isAlive())   &lt;br /&gt;            throw new IllegalArgumentException(&amp;quot;Hook already running&amp;quot;);   &lt;br /&gt;   &lt;br /&gt;        if (hooks.containsKey(hook))   &lt;br /&gt;            throw new IllegalArgumentException(&amp;quot;Hook previously registered&amp;quot;);   &lt;br /&gt;   &lt;br /&gt;        hooks.put(hook, hook);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    static void runHooks() {   &lt;br /&gt;        Collection&amp;lt;Thread&amp;gt; threads;   &lt;br /&gt;        synchronized(ApplicationShutdownHooks.class) {   &lt;br /&gt;            threads = hooks.keySet();   &lt;br /&gt;            hooks = null;   &lt;br /&gt;        }   &lt;br /&gt;        // 顺序启动 shutdownhooks   &lt;br /&gt;        for (Thread hook : threads) {   &lt;br /&gt;            hook.start();   &lt;br /&gt;        }   &lt;br /&gt;        // 并发调用 shutdownhooks ，等待所有 hooks 运行完毕退出   &lt;br /&gt;        for (Thread hook : threads) {   &lt;br /&gt;            try {   &lt;br /&gt;                hook.join();   &lt;br /&gt;            } catch (InterruptedException x) { }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;ApplicationShutdownHooks 类中也有一个集合  &lt;code&gt;IdentityHashMap&amp;lt;Thread, Thread&amp;gt; hooks&lt;/code&gt;，专门用来存放由用户自定义的 Application hooks 类型的 ShutdownHook 。通过 ApplicationShutdownHooks#add 方法添加进 hooks 集合中。&lt;/p&gt; &lt;p&gt;然后在 runHooks 方法里挨个启动 ShutdownHook 线程，并发执行。  &lt;strong&gt;注意这里的 runHooks 方法是 ApplicationShutdownHooks 类中的&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;在 ApplicationShutdownHooks 类的静态代码块 static{.....} 中会将 runHooks 方法封装成 Runnable 添加进 Shutdown 类中的 hooks 数组中。注意这里 Shutdown#add 方法传递进的索引是 1 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;class ApplicationShutdownHooks {   &lt;br /&gt;    /* The set of registered hooks */   &lt;br /&gt;    private static IdentityHashMap&amp;lt;Thread, Thread&amp;gt; hooks;   &lt;br /&gt;   &lt;br /&gt;    static {   &lt;br /&gt;        try {   &lt;br /&gt;            Shutdown.add(1 /* shutdown hook invocation order */,   &lt;br /&gt;                false /* not registered if shutdown in progress */,   &lt;br /&gt;                new Runnable() {   &lt;br /&gt;                    public void run() {   &lt;br /&gt;                        runHooks();   &lt;br /&gt;                    }   &lt;br /&gt;                }   &lt;br /&gt;            );   &lt;br /&gt;            hooks = new IdentityHashMap&amp;lt;&amp;gt;();   &lt;br /&gt;        } catch (IllegalStateException e) {   &lt;br /&gt;            // application shutdown hooks cannot be added if   &lt;br /&gt;            // shutdown is in progress.   &lt;br /&gt;            hooks = null;   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;img&gt;&lt;/img&gt;Shutdownhook的执行.png &lt;p&gt;Shutdown#add 方法的逻辑就很简单了：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;class Shutdown {   &lt;br /&gt;   &lt;br /&gt;    private static final int MAX_SYSTEM_HOOKS = 10;   &lt;br /&gt;    private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];   &lt;br /&gt;   &lt;br /&gt;    static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {   &lt;br /&gt;        synchronized (lock) {   &lt;br /&gt;            if (hooks[slot] != null)   &lt;br /&gt;                throw new InternalError(&amp;quot;Shutdown hook at slot &amp;quot; + slot + &amp;quot; already registered&amp;quot;);   &lt;br /&gt;   &lt;br /&gt;            if (!registerShutdownInProgress) {   &lt;br /&gt;                if (state &amp;gt; RUNNING)   &lt;br /&gt;                    throw new IllegalStateException(&amp;quot;Shutdown in progress&amp;quot;);   &lt;br /&gt;            } else {   &lt;br /&gt;                if (state &amp;gt; HOOKS || (state == HOOKS &amp;amp;&amp;amp; slot &amp;lt;= currentRunningHook))   &lt;br /&gt;                    throw new IllegalStateException(&amp;quot;Shutdown in progress&amp;quot;);   &lt;br /&gt;            }   &lt;br /&gt;   &lt;br /&gt;            hooks[slot] = hook;   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;参数 Runnable hook 就是在 ApplicationShutdownHooks 中的静态代码块 static{....} 中将 runHooks 方法封装成的 Runnable。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;参数 int slot 表示将封装好的 Runnable 放入 hooks 数组中的哪个槽中。这里我们注册的是 Application hooks 类型的 ShutdonwHook ，所以这里的索引为 1 。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;参数 registerShutdownInProgress 表示是否允许在 JVM 关闭流程开始之后，继续向 JVM 添加 ShutdownHook 。默认为 false 表示不允许。否则将会抛出 IllegalStateException 异常。这一点笔者在小节《3.2 使用ShutdownHook的注意事项》中强调过。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;以上就是 JVM 如何捕获操作系统内核信号，如何注册 ShutdownHook ，以及何时触发 ShutdownHook 的执行的一个全面介绍。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;shutdownhook完整触发时机.png &lt;blockquote&gt;  &lt;p&gt;读到这里大家应该彻底明白了为什么不能使用 kill -9 pid 命令来关闭进程了吧，现在赶快去检查一下你们公司生产环境的运维脚本吧！！&lt;/p&gt;&lt;/blockquote&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;俗话说的好 talk is cheap! show me the code! ，在介绍了这么多关于优雅关闭的理论方案和原理之后，我想大家现在一定很好奇究竟我们该如何实现这一套优雅关闭的方案呢？&lt;/p&gt; &lt;p&gt;那么接下来笔者就从一些知名框架源码实现角度，为大家详细阐述一下优雅关闭是如何实现的？&lt;/p&gt; &lt;p&gt;                           &lt;img&gt;&lt;/img&gt;&lt;/p&gt;image.png &lt;h2&gt;4. Spring 的优雅关闭机制&lt;/h2&gt; &lt;p&gt;前面两个小节中我们从支持优雅关闭最底层的内核信号机制开始聊起然后到 JVM 进程实现优雅关闭的 ShutdwonHook 原理，经过这一系列的介绍，我们现在对优雅关闭在内核层和 JVM 层的相关机制原理有了一定的了解。&lt;/p&gt; &lt;p&gt;那么在真实 Java 应用中，我们到底该如何基于上述机制实现一套优雅关闭方案呢？本小节我们来从 Spring 源码中获取下答案！！&lt;/p&gt; &lt;p&gt;在介绍 Spring 优雅关闭机制源码实现之前，笔者先来带大家回顾下，在 Spring 的应用上下文关闭的时候，Spring 究竟给我们提供了哪些关闭时的回调机制，从而可以让我们在这些回调中编写 Java 应用的优雅关闭逻辑。&lt;/p&gt; &lt;h3&gt;4.1 发布 ContextClosedEvent 事件&lt;/h3&gt; &lt;p&gt;在 Spring 上下文开始关闭的时候，首先会发布 ContextClosedEvent 事件，注意此时 Spring 容器的 Bean 还没有开始销毁，所以我们可以在该事件回调中执行优雅关闭的操作。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@Component   &lt;br /&gt;public class ShutdownListener implements ApplicationListener&amp;lt;ContextClosedEvent&amp;gt; {   &lt;br /&gt;       @Override   &lt;br /&gt;       public void onApplicationEvent(ContextClosedEvent event) {   &lt;br /&gt;                  ........优雅关闭逻辑.....   &lt;br /&gt;       }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;4.2 Spring 容器中的 Bean 销毁前回调&lt;/h3&gt; &lt;p&gt;当 Spring 开始销毁容器中管理的 Bean 之前，会回调所有实现 DestructionAwareBeanPostProcessor 接口的 Bean 中的 postProcessBeforeDestruction 方法。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@Component   &lt;br /&gt;public class DestroyBeanPostProcessor implements DestructionAwareBeanPostProcessor {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {   &lt;br /&gt;   &lt;br /&gt;             ........Spring容器中的Bean开始销毁前回调.......   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;4.3 回调标注 @PreDestroy 注解的方法&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;@Component   &lt;br /&gt;public class Shutdown {   &lt;br /&gt;    @PreDestroy   &lt;br /&gt;    public void preDestroy() {   &lt;br /&gt;        ......释放资源.......   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;4.4 回调 DisposableBean 接口中的 destroy 方法&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;@Component   &lt;br /&gt;public class Shutdown implements DisposableBean{   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void destroy() throws Exception {   &lt;br /&gt;         ......释放资源......   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;4.5 回调自定义的销毁方法&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;bean id=&amp;quot;Shutdown&amp;quot; class=&amp;quot;com.test.netty.Shutdown&amp;quot;  destroy-method=&amp;quot;doDestroy&amp;quot;/&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;public class Shutdown {   &lt;br /&gt;   &lt;br /&gt;    public void doDestroy() {   &lt;br /&gt;        .....自定义销毁方法....   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;4.6 Spring 优雅关闭机制的实现&lt;/h3&gt; &lt;p&gt;Spring 相关应用程序本质上也是一个 JVM 进程，所以 Spring 框架想要实现优雅关闭机制也必须依托于我们在本文第三小节中介绍的 JVM 的 ShutdownHook 机制。&lt;/p&gt; &lt;p&gt;在 Spring 启动的时候，需要向 JVM 注册 ShutdownHook ，当我们执行  &lt;code&gt;kill - 15 pid&lt;/code&gt;命令时，随后 Spring 会在 ShutdownHook 中触发上述介绍的五种回调。&lt;/p&gt; &lt;p&gt;下面我们来看一下 Spring 中 ShutdownHook 的注册逻辑：&lt;/p&gt; &lt;h4&gt;4.6.1 Spring 中 ShutdownHook 的注册&lt;/h4&gt; &lt;pre&gt;  &lt;code&gt;public abstract class AbstractApplicationContext extends DefaultResourceLoader   &lt;br /&gt;  implements ConfigurableApplicationContext, DisposableBean {   &lt;br /&gt;   &lt;br /&gt; @Override   &lt;br /&gt; public void registerShutdownHook() {   &lt;br /&gt;  if (this.shutdownHook == null) {   &lt;br /&gt;   // No shutdown hook registered yet.   &lt;br /&gt;   this.shutdownHook = new Thread() {   &lt;br /&gt;    @Override   &lt;br /&gt;    public void run() {   &lt;br /&gt;     synchronized (startupShutdownMonitor) {   &lt;br /&gt;      doClose();   &lt;br /&gt;     }   &lt;br /&gt;    }   &lt;br /&gt;   };   &lt;br /&gt;   Runtime.getRuntime().addShutdownHook(this.shutdownHook);   &lt;br /&gt;  }   &lt;br /&gt; }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在 Spring 启动的时候，我们需要调用  &lt;code&gt;AbstractApplicationContext#registerShutdownHook&lt;/code&gt;方法向 JVM 注册 Spring 的 ShutdownHook ，从这段源码中我们看出，Spring 将 doClose() 方法封装在 ShutdownHook 线程中，而 doClose() 方法里边就是 Spring 优雅关闭的逻辑。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;这里需要强调的是，当我们在一个纯 Spring 环境下&lt;/strong&gt;，Spring 框架是不会为我们主动调用 registerShutdownHook 方法去向 JVM 注册 ShutdownHook 的，我们需要手动调用 registerShutdownHook 方法去注册。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class SpringShutdownHook {   &lt;br /&gt;   &lt;br /&gt;    public static void main(String[] args) throws IOException {   &lt;br /&gt;        GenericApplicationContext context = new GenericApplicationContext();   &lt;br /&gt;                      ........   &lt;br /&gt;        // 注册 Shutdown Hook   &lt;br /&gt;        context.registerShutdownHook();   &lt;br /&gt;                      ........   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;而在 SpringBoot 环境下&lt;/strong&gt;，SpringBoot 在启动的时候会为我们调用这个方法去主动注册 ShutdownHook 。我们不需要手动注册。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class SpringApplication {   &lt;br /&gt;   &lt;br /&gt; public ConfigurableApplicationContext run(String... args) {   &lt;br /&gt;   &lt;br /&gt;                  ...............省略.................   &lt;br /&gt;   &lt;br /&gt;                  ConfigurableApplicationContext context = null;   &lt;br /&gt;                  context = createApplicationContext();   &lt;br /&gt;                  refreshContext(context);   &lt;br /&gt;   &lt;br /&gt;                  ...............省略.................   &lt;br /&gt; }   &lt;br /&gt;   &lt;br /&gt; private void refreshContext(ConfigurableApplicationContext context) {   &lt;br /&gt;  refresh(context);   &lt;br /&gt;  if (this.registerShutdownHook) {   &lt;br /&gt;   try {   &lt;br /&gt;    context.registerShutdownHook();   &lt;br /&gt;   }   &lt;br /&gt;   catch (AccessControlException ex) {   &lt;br /&gt;    // Not allowed in some environments.   &lt;br /&gt;   }   &lt;br /&gt;  }   &lt;br /&gt; }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;4.6.2 Spring 中的优雅关闭逻辑&lt;/h4&gt; &lt;pre&gt;  &lt;code&gt; protected void doClose() {   &lt;br /&gt;  // 更新上下文状态   &lt;br /&gt;  if (this.active.get() &amp;amp;&amp;amp; this.closed.compareAndSet(false, true)) {   &lt;br /&gt;   if (logger.isInfoEnabled()) {   &lt;br /&gt;    logger.info(&amp;quot;Closing &amp;quot; + this);   &lt;br /&gt;   }   &lt;br /&gt;            // 取消 JMX 托管   &lt;br /&gt;   LiveBeansView.unregisterApplicationContext(this);   &lt;br /&gt;   &lt;br /&gt;   try {   &lt;br /&gt;    // 发布 ContextClosedEvent 事件   &lt;br /&gt;    publishEvent(new ContextClosedEvent(this));   &lt;br /&gt;   }   &lt;br /&gt;   catch (Throwable ex) {   &lt;br /&gt;    logger.warn(&amp;quot;Exception thrown from ApplicationListener handling ContextClosedEvent&amp;quot;, ex);   &lt;br /&gt;   }   &lt;br /&gt;   &lt;br /&gt;   // 回调 Lifecycle beans,相关 stop 方法   &lt;br /&gt;   if (this.lifecycleProcessor != null) {   &lt;br /&gt;    try {   &lt;br /&gt;     this.lifecycleProcessor.onClose();   &lt;br /&gt;    }   &lt;br /&gt;    catch (Throwable ex) {   &lt;br /&gt;     logger.warn(&amp;quot;Exception thrown from LifecycleProcessor on context close&amp;quot;, ex);   &lt;br /&gt;    }   &lt;br /&gt;   }   &lt;br /&gt;   &lt;br /&gt;   // 销毁 bean，触发前面介绍的几种回调   &lt;br /&gt;   destroyBeans();   &lt;br /&gt;   &lt;br /&gt;   // Close the state of this context itself.   &lt;br /&gt;   closeBeanFactory();   &lt;br /&gt;   &lt;br /&gt;   // Let subclasses do some final clean-up if they wish...   &lt;br /&gt;   onClose();   &lt;br /&gt;   &lt;br /&gt;   // Switch to inactive.   &lt;br /&gt;   this.active.set(false);   &lt;br /&gt;  }   &lt;br /&gt; }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在这里我们可以看出最终是在 AbstractApplicationContext#doClose 方法中触发本小节开始介绍的五种回调：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;发布 ContextClosedEvent 事件。   &lt;strong&gt;注意这里是一个同步事件&lt;/strong&gt;，也就是说 Spring 的 ShutdownHook 线程在这里发布完事件之后会继续同步执行事件的处理，等到事件处理完毕后，才会去执行后面的 destroyBeans() 方法对 IOC 容器中的 Bean 进行销毁。&lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt;  &lt;p&gt;所以在 ContextClosedEvent 事件监听类中，可以放心地去做优雅关闭相关的操作，因为此时 Spring 容器中的 Bean 还没有被销毁。&lt;/p&gt;&lt;/blockquote&gt; &lt;ol start="2"&gt;  &lt;li&gt;destroyBeans() 方法中依次触发剩下的四种回调。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;最后结合前边小节中介绍的内容，总结 Spring 的整个优雅关闭流程如下图所示：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Spring优雅关闭机制.png &lt;h2&gt;5. Dubbo 的优雅关闭&lt;/h2&gt; &lt;blockquote&gt;  &lt;p&gt;本小节优雅关闭部分源码基于 apache dubbo   &lt;strong&gt;2.7.7&lt;/strong&gt;版本，该版本中的优雅关闭是有 Bug 的，下面让我们一起来 Shooting Bug !&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;在前边几个小节的内容中，我们从内核提供的底层技术支持开始聊到了 JVM 的 ShutdonwHook ，然后又从 JVM 聊到了 Spring 框架的优雅关闭机制。&lt;/p&gt; &lt;p&gt;在了解了这些内容之后，本小节我们就来看下 dubbo 中的优雅关闭实现，由于现在几乎所有 Java 应用都会采用 Spring 作为开发框架，所以 dubbo 一般是集成在 Spring 框架中供我们使用的，它的优雅关闭和 Spring 有着紧密的联系。&lt;/p&gt; &lt;h3&gt;5.1 Dubbo 在 Spring 环境下的优雅关闭&lt;/h3&gt; &lt;p&gt;在本文第四小节《4. Spring的优雅关闭机制》的介绍中，我们知道在 Spring 的优雅关闭流程中，Spring 的 ShutdownHook 线程会首先发布 ContextClosedEvent 事件，  &lt;strong&gt;该事件是一个同步事件&lt;/strong&gt;，ShutdownHook 线程发布完该事件紧接着就会同步执行该事件的监听器，当在事件监听器中处理完 ContextClosedEvent 事件之后，在回过头来执行 destroyBeans() 方法并依次触发剩下的四种回调来销毁 IOC 容器中的 Bean 。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Spring优雅关闭流程.png &lt;p&gt;由于在处理 ContextClosedEvent 事件的时候，Dubbo 所依赖的一些关键 bean 这时还没有被销毁，所以 dubbo 定义了一个 DubboBootstrapApplicationListener 用来监听 ContextClosedEvent 事件，并在 onContextClosedEvent 事件处理方法中调用 dubboBootstrap.stop() 方法开启 dubbo 的优雅关闭流程。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener   &lt;br /&gt;        implements Ordered {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void onApplicationContextEvent(ApplicationContextEvent event) {   &lt;br /&gt;        // 这里是 Spring 的同步事件，publishEvent 和处理 Event 是在同一个线程中   &lt;br /&gt;        if (event instanceof ContextRefreshedEvent) {   &lt;br /&gt;            onContextRefreshedEvent((ContextRefreshedEvent) event);   &lt;br /&gt;        } else if (event instanceof ContextClosedEvent) {   &lt;br /&gt;            onContextClosedEvent((ContextClosedEvent) event);   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    private void onContextClosedEvent(ContextClosedEvent event) {   &lt;br /&gt;        // spring 在 shutdownhook 中会先触发 ContextClosedEvent ，然后在销毁 spring beans   &lt;br /&gt;        // 所以这里 dubbo 开始优雅关闭时，依赖的 spring beans 并未销毁   &lt;br /&gt;        dubboBootstrap.stop();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当服务提供者 ServiceBean 和服务消费者 ReferenceBean 被初始化时,会将 DubboBootstrapApplicationListener 注册到 Spring 容器中。并开始监听 ContextClosedEvent 事件和 ContextRefreshedEvent 事件。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,   &lt;br /&gt;        ResourceLoaderAware, BeanClassLoaderAware {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {   &lt;br /&gt;   &lt;br /&gt;        // @since 2.7.5 注册spring启动 关闭事件的listener   &lt;br /&gt;        //在事件回调中中调用启动类 DubboBootStrap的start  stop来启动 关闭dubbo应用   &lt;br /&gt;        registerBeans(registry, DubboBootstrapApplicationListener.class);   &lt;br /&gt;         &lt;br /&gt;                  ........省略.......   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;5.2 Dubbo 优雅关闭流程简介&lt;/h3&gt; &lt;blockquote&gt;  &lt;p&gt;由于本文的主题是介绍优雅关闭的一整条流程主线，所以这里笔者只是简要介绍 Dubbo 优雅关闭的主流程，相关细节部分笔者会在后续的 dubbo 源码解析系列里为大家详细介绍 Dubbo 优雅关闭的细节。为了避免本文发散太多，我们这里还是聚焦于流程主线。&lt;/p&gt;&lt;/blockquote&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap extends GenericEventListener {   &lt;br /&gt;   &lt;br /&gt;    public DubboBootstrap stop() throws IllegalStateException {   &lt;br /&gt;        destroy();   &lt;br /&gt;        return this;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里的核心逻辑其实就是我们在《1.2 优雅关闭》小节中介绍的两大优雅关闭主题：&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;blockquote&gt;  &lt;p&gt;这里大家只需要了解 Dubbo 优雅关闭的主流程即可，相关细节笔者后续会有一篇专门的文章详细为大家介绍。&lt;/p&gt;&lt;/blockquote&gt; &lt;pre&gt;  &lt;code&gt;    public void destroy() {   &lt;br /&gt;        if (destroyLock.tryLock()) {   &lt;br /&gt;            try {   &lt;br /&gt;                DubboShutdownHook.destroyAll();   &lt;br /&gt;   &lt;br /&gt;                if (started.compareAndSet(true, false)   &lt;br /&gt;                        &amp;amp;&amp;amp; destroyed.compareAndSet(false, true)) {   &lt;br /&gt;   &lt;br /&gt;                    //取消注册   &lt;br /&gt;                    unregisterServiceInstance();   &lt;br /&gt;                    //取消元数据服务   &lt;br /&gt;                    unexportMetadataService();   &lt;br /&gt;                    //停止暴露服务   &lt;br /&gt;                    unexportServices();   &lt;br /&gt;                    //取消订阅服务   &lt;br /&gt;                    unreferServices();   &lt;br /&gt;                    //注销注册中心   &lt;br /&gt;                    destroyRegistries();   &lt;br /&gt;                    //关闭服务   &lt;br /&gt;                    DubboShutdownHook.destroyProtocols();   &lt;br /&gt;                    //销毁注册中心客户端实例   &lt;br /&gt;                    destroyServiceDiscoveries();   &lt;br /&gt;                    //清除应用配置类以及相关应用模型   &lt;br /&gt;                    clear();   &lt;br /&gt;                    //关闭线程池   &lt;br /&gt;                    shutdown();   &lt;br /&gt;                    //释放资源   &lt;br /&gt;                    release();   &lt;br /&gt;                }   &lt;br /&gt;            } finally {   &lt;br /&gt;                destroyLock.unlock();   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;从以上内容可以看出，Dubbo 的优雅关闭依托于 Spring ContextClosedEvent 事件的发布，而 ContextClosedEvent 事件的发布又依托于 Spring ShutdownHook 的注册。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;dubbo spring环境优雅关闭.png &lt;p&gt;从《4.6.1 Spring 中 ShutdownHook 的注册》小节的介绍中我们知道，在 SpringBoot 环境下，SpringBoot 在启动的时候会为我们调用  &lt;code&gt;ApplicationContext#registerShutdownHook&lt;/code&gt;方法去主动注册 ShutdownHook 。我们不需要手动注册。&lt;/p&gt; &lt;p&gt;而在一个纯 Spring 环境下，Spring 框架并不会为我们主动调用 registerShutdownHook 方法去向 JVM 注册 ShutdownHook 的，我们需要手动调用 registerShutdownHook 方法去注册。&lt;/p&gt; &lt;p&gt;所以 Dubbo 这里为了兼容 SpringBoot 环境和纯 Spring 环境下的优雅关闭，引入了  &lt;code&gt;SpringExtensionFactory类&lt;/code&gt;，只要在 Spring 环境下都会调用 registerShutdownHook 去向 JVM 注册 Spring 的 ShutdownHook 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class SpringExtensionFactory implements ExtensionFactory {   &lt;br /&gt;    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);   &lt;br /&gt;   &lt;br /&gt;    private static final Set&amp;lt;ApplicationContext&amp;gt; CONTEXTS = new ConcurrentHashSet&amp;lt;ApplicationContext&amp;gt;();   &lt;br /&gt;   &lt;br /&gt;    public static void addApplicationContext(ApplicationContext context) {   &lt;br /&gt;        CONTEXTS.add(context);   &lt;br /&gt;        if (context instanceof ConfigurableApplicationContext) {   &lt;br /&gt;            //在spring启动成功之后设置shutdownHook（兼容非SpringBoot环境）   &lt;br /&gt;            ((ConfigurableApplicationContext) context).registerShutdownHook();   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当服务提供者 ServiceBean 和服务消费者 ReferenceBean 在初始化完成之后，会回调  &lt;code&gt;SpringExtensionFactory#addApplicationContext&lt;/code&gt;方法注册 ShutdownHook 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class ServiceBean&amp;lt;T&amp;gt; extends ServiceConfig&amp;lt;T&amp;gt; implements InitializingBean, DisposableBean,   &lt;br /&gt;        ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {   &lt;br /&gt;   &lt;br /&gt;   @Override   &lt;br /&gt;    public void setApplicationContext(ApplicationContext applicationContext) {   &lt;br /&gt;        this.applicationContext = applicationContext;   &lt;br /&gt;        SpringExtensionFactory.addApplicationContext(applicationContext);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;public class ReferenceBean&amp;lt;T&amp;gt; extends ReferenceConfig&amp;lt;T&amp;gt; implements FactoryBean,   &lt;br /&gt;        ApplicationContextAware, InitializingBean, DisposableBean {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void setApplicationContext(ApplicationContext applicationContext) {   &lt;br /&gt;        this.applicationContext = applicationContext;   &lt;br /&gt;        SpringExtensionFactory.addApplicationContext(applicationContext);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上就是 Dubbo 在 Spring 集成环境下的优雅关闭全流程，下面我们来看下 Dubbo 在非 Spring 环境下的优雅关闭流程。&lt;/p&gt; &lt;h3&gt;5.3 Dubbo 在非 Spring 环境下的优雅关闭&lt;/h3&gt; &lt;p&gt;在上小节的介绍中我们知道 Dubbo 在 Spring 环境下依托 Spring 的 ShutdownHook ，通过监听 ContextClosedEvent 事件，从而触发 Dubbo 的优雅关闭流程。&lt;/p&gt; &lt;p&gt;而到了非 Spring 环境下，Dubbo 就需要定义自己的 ShutdownHook ，从而引入了 DubboShutdownHook ，直接将优雅关闭流程封装在自己的 ShutdownHook 中执行。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap extends GenericEventListener {   &lt;br /&gt;   &lt;br /&gt;    private DubboBootstrap() {   &lt;br /&gt;        configManager = ApplicationModel.getConfigManager();   &lt;br /&gt;        environment = ApplicationModel.getEnvironment();   &lt;br /&gt;   &lt;br /&gt;        DubboShutdownHook.getDubboShutdownHook().register();   &lt;br /&gt;        ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {   &lt;br /&gt;            @Override   &lt;br /&gt;            public void callback() throws Throwable {   &lt;br /&gt;                DubboBootstrap.this.destroy();   &lt;br /&gt;            }   &lt;br /&gt;        });   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;public class DubboShutdownHook extends Thread {   &lt;br /&gt;   &lt;br /&gt;   public void register() {   &lt;br /&gt;        if (registered.compareAndSet(false, true)) {   &lt;br /&gt;            DubboShutdownHook dubboShutdownHook = getDubboShutdownHook();   &lt;br /&gt;            Runtime.getRuntime().addShutdownHook(dubboShutdownHook);   &lt;br /&gt;            dispatch(new DubboShutdownHookRegisteredEvent(dubboShutdownHook));   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void run() {   &lt;br /&gt;        if (logger.isInfoEnabled()) {   &lt;br /&gt;            logger.info(&amp;quot;Run shutdown hook now.&amp;quot;);   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        callback();   &lt;br /&gt;        doDestroy();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;   private void callback() {   &lt;br /&gt;        callbacks.callback();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从源码中我们看到，当我们的 Dubbo 应用程序接收到  &lt;code&gt;kill -15 pid&lt;/code&gt;信号时，JVM 捕获到 SIGTERM(15) 信号之后，就会触发 DubboShutdownHook 线程运行，从而通过 callback() 又回调了上小节中介绍的 DubboBootstrap#destroy 方法（dubbo 的整个优雅关闭逻辑全部封装在这里）。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;dubbo 非Spring环境下优雅关闭流程.png &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap extends GenericEventListener {   &lt;br /&gt;   &lt;br /&gt;    public void destroy() {   &lt;br /&gt;        if (destroyLock.tryLock()) {   &lt;br /&gt;            try {   &lt;br /&gt;                DubboShutdownHook.destroyAll();   &lt;br /&gt;   &lt;br /&gt;                if (started.compareAndSet(true, false)   &lt;br /&gt;                        &amp;amp;&amp;amp; destroyed.compareAndSet(false, true)) {   &lt;br /&gt;   &lt;br /&gt;                    ........取消注册......   &lt;br /&gt;                     &lt;br /&gt;                    ........取消元数据服务........   &lt;br /&gt;                     &lt;br /&gt;                    ........停止暴露服务........   &lt;br /&gt;                    &lt;br /&gt;                    ........取消订阅服务........   &lt;br /&gt;                    &lt;br /&gt;                    ........注销注册中心........   &lt;br /&gt;                    &lt;br /&gt;                    ........关闭服务........   &lt;br /&gt;                     &lt;br /&gt;                    ........销毁注册中心客户端实例........   &lt;br /&gt;                    &lt;br /&gt;                    ........清除应用配置类以及相关应用模型........   &lt;br /&gt;                   &lt;br /&gt;                    ........关闭线程池........   &lt;br /&gt;                    &lt;br /&gt;                    ........释放资源........   &lt;br /&gt;                    &lt;br /&gt;                }   &lt;br /&gt;            } finally {   &lt;br /&gt;                destroyLock.unlock();   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;5.4 啊哈！Bug!&lt;/h3&gt; &lt;p&gt;前边我们在《5.1 Dubbo在Spring环境下的优雅关闭》小节和《5.3 Dubbo在非Spring环境下的优雅关闭》小节中介绍的这两个环境的下的优雅关闭方案，当它们在各自的场景下运行的时候是没有任何问题的。&lt;/p&gt; &lt;p&gt;但是当这两种方案结合在一起运行，就出大问题了~~~&lt;/p&gt; &lt;p&gt;还记得笔者在《3.2 使用 ShutdownHook 的注意事项》小节中特别强调的一点：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;ShutdownHook 其实本质上是一个已经被初始化但是未启动的 Thread ，这些通过   &lt;code&gt;Runtime.getRuntime().addShutdownHook&lt;/code&gt;方法注册的 ShutdownHooks ，在 JVM 进程关闭的时候会被启动   &lt;strong&gt;并发执行，但是并不会保证执行顺序&lt;/strong&gt;。&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;所以在编写 ShutdownHook 中的逻辑时，我们应该确保程序的线程安全性，并尽可能避免死锁。最好是一个 JVM 进程只注册一个 ShutdownHook 。&lt;/p&gt;&lt;/blockquote&gt; &lt;img&gt;&lt;/img&gt;Dubbo在Spring环境下的优雅关闭Bug.png &lt;p&gt;那么现在 JVM 中我们注册了两个 ShutdownHook 线程，一个 Spring 的 ShutdownHook ，另一个是 Dubbo 的 ShutdonwHook 。那么这会引出什么问题呢？&lt;/p&gt; &lt;p&gt;经过前边的内容介绍我们知道，无论是在 Spring 的 ShutdownHook 中触发的 ContextClosedEvent 事件还是在 Dubbo 的 ShutdownHook 中执行的 CallBack 。最终都会调用到  &lt;code&gt;DubboBootstrap#destroy&lt;/code&gt;方法执行真正的优雅关闭逻辑。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap extends GenericEventListener {   &lt;br /&gt;   &lt;br /&gt;    private final Lock destroyLock = new ReentrantLock();   &lt;br /&gt;   &lt;br /&gt;    public void destroy() {   &lt;br /&gt;        if (destroyLock.tryLock()) {   &lt;br /&gt;            try {   &lt;br /&gt;                DubboShutdownHook.destroyAll();   &lt;br /&gt;   &lt;br /&gt;                if (started.compareAndSet(true, false)   &lt;br /&gt;                        &amp;amp;&amp;amp; destroyed.compareAndSet(false, true)) {   &lt;br /&gt;                       &lt;br /&gt;                        .......dubbo应用的优雅关闭.......   &lt;br /&gt;                    &lt;br /&gt;                }   &lt;br /&gt;            } finally {   &lt;br /&gt;                destroyLock.unlock();   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;让我们来设想一个这种的场景：当 Spring 的 ShutdownHook 线程和 Dubbo 的 ShutdownHook 线程同时执行并且在同一个时间点来到 DubboBootstrap#destroy 方法中争夺 destroyLock 。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;Dubbo 的 ShutdownHook 线程获得 destroyLock 进入 destroy() 方法体开始执行优雅关闭逻辑。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Spring 的 ShutdownHook 线程没有获得 destroyLock，退出 destroy() 方法。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;img&gt;&lt;/img&gt;Dubbo优雅关闭Bug.png &lt;p&gt;在 Spring 的 ShutdownHook 线程退出 destroy() 方法之后紧接着就会执行 destroyBeans() 方法销毁 IOC 容器中的 Bean ，这里边肯定涉及到一些关键业务 Bean 的销毁，比如：数据库连接池，以及 Dubbo 相关的核心 Bean。&lt;/p&gt; &lt;p&gt;于此同时 Dubbo 的 ShutdownHook 线程开始执行优雅关闭逻辑，《1.2 优雅关闭》小节中我们提到，优雅关闭要保证业务无损。所以需要将剩下正在进行中的业务流程继续处理完毕并将业务处理结果响应给客户端。但是这时依赖的一些业务关键 Bean 已经被销毁，比如数据库连接池，这时执行数据库操作就会抛出  &lt;code&gt;CannotGetJdbcConnectionException&lt;/code&gt;。导致优雅关闭失败，对业务造成了影响。&lt;/p&gt; &lt;h3&gt;5.5 Bug 的修复&lt;/h3&gt; &lt;blockquote&gt;  &lt;p&gt;该 Bug 最终在   &lt;strong&gt;apache dubbo 2.7.15&lt;/strong&gt;版本中被修复&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;详情可查看Issue：https://github.com/apache/dubbo/issues/7093&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;经过上小节的分析，我们知道既然这个 Bug 产生的原因是由于 Spring 的 ShutdownHook 线程和 Dubbo 的 ShutdownHook 线程并发执行所导致的。&lt;/p&gt; &lt;p&gt;那么当我们处于 Spring 环境下的时候，就将 Dubbo 的 ShutdownHook 注销掉即可。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class SpringExtensionFactory implements ExtensionFactory {   &lt;br /&gt;    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);   &lt;br /&gt;   &lt;br /&gt;    private static final Set&amp;lt;ApplicationContext&amp;gt; CONTEXTS = new ConcurrentHashSet&amp;lt;ApplicationContext&amp;gt;();   &lt;br /&gt;   &lt;br /&gt;    public static void addApplicationContext(ApplicationContext context) {   &lt;br /&gt;        CONTEXTS.add(context);   &lt;br /&gt;        if (context instanceof ConfigurableApplicationContext) {   &lt;br /&gt;            // 注册 Spring 的 ShutdownHook   &lt;br /&gt;            ((ConfigurableApplicationContext) context).registerShutdownHook();   &lt;br /&gt;            // 在 Spring 环境下将 Dubbo 的 ShutdownHook 取消掉   &lt;br /&gt;            DubboShutdownHook.getDubboShutdownHook().unregister();   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而在非 Spring 环境下，我们依然保留 Dubbo 的 ShutdownHook 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap {   &lt;br /&gt;   &lt;br /&gt;    private DubboBootstrap() {   &lt;br /&gt;        configManager = ApplicationModel.getConfigManager();   &lt;br /&gt;        environment = ApplicationModel.getEnvironment();   &lt;br /&gt;   &lt;br /&gt;        DubboShutdownHook.getDubboShutdownHook().register();   &lt;br /&gt;        ShutdownHookCallbacks.INSTANCE.addCallback(DubboBootstrap.this::destroy);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上内容就是 Dubbo 的整个优雅关闭主线流程，以及优雅关闭 Bug 产生的原因和修复方案。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;在 Dubbo 的优雅关闭流程中最终会通过  &lt;code&gt;DubboShutdownHook.destroyProtocols()&lt;/code&gt;关闭底层服务。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboBootstrap extends GenericEventListener {   &lt;br /&gt;   &lt;br /&gt;    private final Lock destroyLock = new ReentrantLock();   &lt;br /&gt;   &lt;br /&gt;    public void destroy() {   &lt;br /&gt;        if (destroyLock.tryLock()) {   &lt;br /&gt;            try {   &lt;br /&gt;                DubboShutdownHook.destroyAll();   &lt;br /&gt;   &lt;br /&gt;                if (started.compareAndSet(true, false)   &lt;br /&gt;                        &amp;amp;&amp;amp; destroyed.compareAndSet(false, true)) {   &lt;br /&gt;                       &lt;br /&gt;                        .......dubbo应用的优雅关闭.......   &lt;br /&gt;                    //关闭服务   &lt;br /&gt;                    DubboShutdownHook.destroyProtocols();   &lt;br /&gt;   &lt;br /&gt;                        .......dubbo应用的优雅关闭.......   &lt;br /&gt;   &lt;br /&gt;                }   &lt;br /&gt;            } finally {   &lt;br /&gt;                destroyLock.unlock();   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在 Dubbo 服务的销毁过程中，会通过调用 server.close 关闭底层的 Netty 服务。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class DubboProtocol extends AbstractProtocol {   &lt;br /&gt;   &lt;br /&gt;   @Override   &lt;br /&gt;    public void destroy() {   &lt;br /&gt;        for (String key : new ArrayList&amp;lt;&amp;gt;(serverMap.keySet())) {   &lt;br /&gt;            ProtocolServer protocolServer = serverMap.remove(key);   &lt;br /&gt;            RemotingServer server = protocolServer.getRemotingServer();   &lt;br /&gt;            server.close(ConfigurationUtils.getServerShutdownTimeout());   &lt;br /&gt;             ...........省略........   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;         ...........省略........   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最终触发 Netty 的优雅关闭。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class NettyServer extends AbstractServer implements RemotingServer {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    protected void doClose() throws Throwable {   &lt;br /&gt;        ..........关闭底层Channel......   &lt;br /&gt;        try {   &lt;br /&gt;            if (bootstrap != null) {   &lt;br /&gt;                // 关闭 Netty 的主从 Reactor 线程组   &lt;br /&gt;                bossGroup.shutdownGracefully();   &lt;br /&gt;                workerGroup.shutdownGracefully();   &lt;br /&gt;            }   &lt;br /&gt;        } catch (Throwable e) {   &lt;br /&gt;            logger.warn(e.getMessage(), e);   &lt;br /&gt;        }   &lt;br /&gt;        .........清理缓存Channel数据.......   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;6. Netty 的优雅关闭&lt;/h2&gt; &lt;p&gt;通过上小节介绍 dubbo 优雅关闭的相关内容，我们很自然的引出了 Netty 的优雅关闭触发时机，那么在本小节中笔者将为大家详细介绍下 Netty 是如何优雅地装..........优雅地谢幕的~~&lt;/p&gt; &lt;p&gt;               &lt;img&gt;&lt;/img&gt;&lt;/p&gt;image.png &lt;p&gt;在之前的系列文章中，我们围绕下图所展示的 Netty 整个核心框架的运转流程介绍了主从 ReactorGroup 的  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247483907&amp;idx=1&amp;sn=084c470a8fe6234c2c9461b5f713ff30&amp;chksm=ce77c444f9004d52e7c6244bee83479070effb0bc59236df071f4d62e91e25f01715fca53696&amp;scene=21#wechat_redirect"&gt;创建&lt;/a&gt;，  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484005&amp;idx=1&amp;sn=52f51269902a58f40d33208421109bc3&amp;chksm=ce77c422f9004d340e5b385ef6ba24dfba1f802076ace80ad6390e934173a10401e64e13eaeb&amp;scene=21#wechat_redirect"&gt;启动&lt;/a&gt;，  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484087&amp;idx=1&amp;sn=0c065780e0f05c23c8e6465ede86cba0&amp;chksm=ce77c4f0f9004de63be369a664105708bc5975b52993f4a6df223caed34cc1ef6185a16acd75&amp;scene=21#wechat_redirect"&gt;运行&lt;/a&gt;，  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484184&amp;idx=1&amp;sn=726877ce28cf6e5d2ac3225fae687f19&amp;chksm=ce77c55ff9004c493b592288819dc4d4664b5949ee97fed977b6558bc517dad0e1f73fab0f46&amp;scene=21#wechat_redirect"&gt;接收网络连接&lt;/a&gt;，  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484244&amp;idx=1&amp;sn=831060fc38caa201d69f87305de7f86a&amp;chksm=ce77c513f9004c05b48f849ff99997d6d7252453135ae856a029137b88aa70b8e046013d596e&amp;scene=21#wechat_redirect"&gt;接收网络数据&lt;/a&gt;，  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484532&amp;idx=1&amp;sn=c3a8b37a2eb09509d9914494ef108c68&amp;chksm=ce77c233f9004b25a29f9fdfb179e41646092d09bc89df2147a9fab66df13231e46dd6a5c26d&amp;scene=21#wechat_redirect"&gt;发送网络数据&lt;/a&gt;，以及  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484823&amp;idx=1&amp;sn=9396fb0f5dbac5e32d0fa1129d385fbc&amp;chksm=ce77c3d0f9004ac678283b7d178835e740eb5e5b80740cc624ae7a307be7a7eb0c7766842878&amp;scene=21#wechat_redirect"&gt;如何在pipeline中处理相关IO事件&lt;/a&gt;的整个源码实现。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;netty中的reactor.png &lt;p&gt;本小节就到了 Netty 优雅谢幕的时刻了，在这谢幕的过程中，Netty 会对它的主从 ReactorGroup ，以及对应 ReactorGroup 中的 Reacto r进行优雅的关闭。下面让我们一起来看下这个优雅关闭的过程~~~&lt;/p&gt; &lt;h3&gt;6.1 ReactorGroup 的优雅谢幕&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;public abstract class AbstractEventExecutorGroup implements EventExecutorGroup {   &lt;br /&gt;   &lt;br /&gt;    static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;   &lt;br /&gt;    static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;   &lt;br /&gt;   &lt;br /&gt;   @Override   &lt;br /&gt;    public Future&amp;lt;?&amp;gt; shutdownGracefully() {   &lt;br /&gt;        return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在 Netty 进行优雅关闭的整个过程中，这里涉及到了两个非常重要的控制参数：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;gracefulShutdownQuietPeriod&lt;/code&gt;：优雅关闭静默期，默认为    &lt;code&gt;2s&lt;/code&gt;。这个参数主要来保证 Netty 整个关闭过程中的    &lt;strong&gt;优雅&lt;/strong&gt;。在关闭流程开始后，如果 Reactor 中还有遗留的异步任务需要执行，那么 Netty 就不能关闭，需要把所有异步任务执行完毕才可以。当所有异步任务执行完毕后，Netty 为了实现更加优雅的关闭操作，一定要保障业务无损，这时候就引入了静默期这个概念，如果在这个静默期内，用户没有新的任务向 Reactor 提交那么就开始关闭。如果在这个静默期内，还有用户继续提交异步任务，那么就不能关闭，需要把静默期内用户提交的异步任务执行完毕才可以放心关闭。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;gracefulShutdownTimeout&lt;/code&gt;：优雅关闭超时时间，默认为    &lt;code&gt;15s&lt;/code&gt;。这个参数主要来保证 Netty 整个关闭过程的    &lt;strong&gt;可控&lt;/strong&gt;。我们知道一个生产级的优雅关闭方案既要保证优雅做到业务无损，更重要的是要保证关闭流程的可控，不能无限制的优雅下去。导致长时间无法完成关闭动作。于是 Netty 就引入了这个参数，如果优雅关闭超时，那么无论此时有无异步任务需要执行都要开始关闭了。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这两个控制参数是非常重要核心的两个参数，我们在后面介绍 Netty 关闭细节的时候还会为大家详细剖析，这里大家先从概念上大概理解一下。&lt;/p&gt; &lt;p&gt;在介绍完这两个重要核心参数之后，我们接下来看下 ReactorGroup 的关闭流程：&lt;/p&gt; &lt;p&gt;我们都知道 Netty 为了保证整个系统的吞吐量以及保证 Reactor 可以线程安全地，有序地处理各个 Channel 上的 IO 事件。基于这个目的 Netty 将其承载的海量连接分摊打散到不同的 Reactor 上处理。&lt;/p&gt; &lt;p&gt;ReactorGroup 中包含多个 Reactor ，每个 Channel 只能注册到一个固定的 Reactor 上，由这个固定的 Reactor 负责处理该 Channel 上整个生命周期的事件。&lt;/p&gt; &lt;p&gt;一个 Reactor 上注册了多个 Channel ，负责处理注册在其上的所有 Channel 的 IO 事件以及异步任务。&lt;/p&gt; &lt;p&gt;ReactorGroup 的结构如下图所示：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;image.png &lt;p&gt;ReactorGroup 的关闭流程本质上其实是 ReactorGroup 中包含的所有 Reactor 的关闭，当 ReactorGroup 中的所有 Reactor 完成关闭后，ReactorGroup 才算是真正的关闭。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {   &lt;br /&gt;   &lt;br /&gt;    // Reactor线程组中的Reactor集合   &lt;br /&gt;    private final EventExecutor[] children;   &lt;br /&gt;   &lt;br /&gt;    // 关闭future   &lt;br /&gt;    private final Promise&amp;lt;?&amp;gt; terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public Future&amp;lt;?&amp;gt; shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {   &lt;br /&gt;        for (EventExecutor l: children) {   &lt;br /&gt;            l.shutdownGracefully(quietPeriod, timeout, unit);   &lt;br /&gt;        }   &lt;br /&gt;        return terminationFuture();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public Future&amp;lt;?&amp;gt; terminationFuture() {   &lt;br /&gt;        return terminationFuture;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;EventExecutor[] children&lt;/code&gt;：数组中存放的是当前 ReactorGroup 中包含的所有 Reactor，类型为 EventExecutor。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;Promise&amp;lt;?&amp;gt; terminationFuture&lt;/code&gt;：ReactorGroup 中的关闭 Future ，用户线程通过这个 terminationFuture 可以知道 ReactorGroup 完成关闭的时机，也可以向 terminationFuture 注册一些 listener 。当 ReactorGroup 完成关闭动作后，会回调用户注册的这些 listener 。大家可以根据各自的业务场景灵活运用。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 ReactorGroup 的关闭过程中，会挨个触发它所包含的所有 Reactor 的关闭流程。并返回 terminationFuture 给用户线程。&lt;/p&gt; &lt;p&gt;当 ReactorGroup 中的所有 Reactor 完成关闭之后，这个 terminationFuture 会被设置为 success，这样一来用户线程可以感知到 ReactorGroup 已经完成关闭了。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;这一点笔者也在   &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247483907&amp;idx=1&amp;sn=084c470a8fe6234c2c9461b5f713ff30&amp;chksm=ce77c444f9004d52e7c6244bee83479070effb0bc59236df071f4d62e91e25f01715fca53696&amp;token=1670680185&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《Reactor在Netty中的实现（创建篇）》&lt;/a&gt;一文中的第四小节《4. 向Reactor线程组中所有的Reactor注册terminated回调函数》强调过。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;在 ReactorGroup 创建的最后一步，会定义 Reactor 关闭的 terminationListener。在 Reactor 的 terminationListener 中会判断当前 ReactorGroup 中的 Reactor 是否全部关闭，如果已经全部关闭，则会设置 ReactorGroup的 terminationFuture 为 success 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;    //记录关闭的Reactor个数，当Reactor全部关闭后，ReactorGroup才可以认为关闭成功   &lt;br /&gt;    private final AtomicInteger terminatedChildren = new AtomicInteger();   &lt;br /&gt;    //ReactorGroup的关闭future   &lt;br /&gt;    private final Promise&amp;lt;?&amp;gt; terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);   &lt;br /&gt;   &lt;br /&gt;    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,   &lt;br /&gt;                                            EventExecutorChooserFactory chooserFactory, Object... args) {   &lt;br /&gt;   &lt;br /&gt;        ........挨个创建Reactor............   &lt;br /&gt;   &lt;br /&gt;        final FutureListener&amp;lt;Object&amp;gt; terminationListener = new FutureListener&amp;lt;Object&amp;gt;() {   &lt;br /&gt;            @Override   &lt;br /&gt;            public void operationComplete(Future&amp;lt;Object&amp;gt; future) throws Exception {   &lt;br /&gt;                if (terminatedChildren.incrementAndGet() == children.length) {   &lt;br /&gt;                    //当所有Reactor关闭后 ReactorGroup才认为是关闭成功   &lt;br /&gt;                    terminationFuture.setSuccess(null);   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        };   &lt;br /&gt;   &lt;br /&gt;        for (EventExecutor e: children) {   &lt;br /&gt;            //向每个Reactor注册terminationListener   &lt;br /&gt;            e.terminationFuture().addListener(terminationListener);   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从以上 ReactorGroup 的关闭流程我们可以看出，ReactorGroup 的关闭逻辑只是挨个去触发它所包含的所有 Reactor 的关闭，Netty 的整个优雅关闭核心其实是在单个 Reactor 的关闭逻辑上。毕竟 Reactor 才是真正驱动 Netty 运转的核心引擎。&lt;/p&gt; &lt;h3&gt;6.2 Reactor 的优雅谢幕&lt;/h3&gt; &lt;img&gt;&lt;/img&gt;Reactor的优雅谢幕流程.png &lt;p&gt;Reactor 的状态特别重要，从  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484087&amp;idx=1&amp;sn=0c065780e0f05c23c8e6465ede86cba0&amp;chksm=ce77c4f0f9004de63be369a664105708bc5975b52993f4a6df223caed34cc1ef6185a16acd75&amp;scene=21#wechat_redirect"&gt;《一文聊透Netty核心引擎Reactor的运转架构》&lt;/a&gt;一文中我们知道 Reactor 是在一个 for (;;) {....} 死循环中 996 不停地工作。比如轮询 Channel 上的 IO 就绪事件，处理 IO 就绪事件，执行异步任务就是在这个死循环中完成的。&lt;/p&gt; &lt;p&gt;而 Reactor 在每一次循环任务结束之后，都会先去判断一下当前 Reactor 的状态，如果状态变为准备关闭状态 ST_SHUTTING_DOWN 后，Reactor 就会开启优雅关闭流程。&lt;/p&gt; &lt;p&gt;所以在介绍 Reactor 的关闭流程之前，笔者先来为大家捋一捋 Reactor 中的各种状态。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ST_NOT_STARTED = 1&lt;/code&gt;：Reactor 的初始状态。在 Reactor 刚被创建出来的时候，状态为 ST_NOT_STARTED 。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ST_STARTED = 2&lt;/code&gt;：Reactor 的启动状态。当向 Reactor 提交第一个异步任务的时候会触发 Reactor 的启动。启动之后状态变为 ST_STARTED 。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;相关细节可在回顾下   &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484005&amp;idx=1&amp;sn=52f51269902a58f40d33208421109bc3&amp;chksm=ce77c422f9004d340e5b385ef6ba24dfba1f802076ace80ad6390e934173a10401e64e13eaeb&amp;token=1670680185&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《详细图解Netty Reactor启动全流程》&lt;/a&gt;一文。&lt;/p&gt;&lt;/blockquote&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ST_SHUTTING_DOWN = 3&lt;/code&gt;：Reactor 准备开始关闭状态。当 Reactor 的 shutdownGracefully 方法被调用的时候，Reactor 的状态就会变为ST_SHUTTING_DOWN。在这个状态下，用户仍然可以向 Reactor 提交任务。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ST_SHUTDOWN = 4&lt;/code&gt;：Reactor 停止状态。表示 Reactor 的优雅关闭流程已经结束，    &lt;strong&gt;此时用户不能在向 Reactor 提交任务&lt;/strong&gt;，Reactor 会在这个状态下最后一次执行剩余的异步任务。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ST_TERMINATED = 5&lt;/code&gt;：Reactor 真正的终结状态，该状态表示 Reactor 已经完全关闭了。在这个状态下 Reactor 会设置自己的 terminationFuture 为 Success。进而开始回调上小节末尾提到的 terminationListener 。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在我们了解了 Reactor 的各种状态之后，下面就该来正式开始介绍 Reactor 的关闭流程了：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {   &lt;br /&gt;   &lt;br /&gt;    //Reactor的状态  初始为未启动状态   &lt;br /&gt;    private volatile int state = ST_NOT_STARTED;   &lt;br /&gt;    &lt;br /&gt;    //Reactor的初始状态，未启动   &lt;br /&gt;    private static final int ST_NOT_STARTED = 1;   &lt;br /&gt;    //Reactor启动后的状态   &lt;br /&gt;    private static final int ST_STARTED = 2;   &lt;br /&gt;    //准备正在进行优雅关闭，此时用户仍然可以提交任务，Reactor仍可以执行任务   &lt;br /&gt;    private static final int ST_SHUTTING_DOWN = 3;   &lt;br /&gt;    //Reactor停止状态，表示优雅关闭结束，此时用户不能在提交任务，Reactor最后一次执行剩余的任务   &lt;br /&gt;    private static final int ST_SHUTDOWN = 4;   &lt;br /&gt;    //Reactor中的任务已被全部执行完毕，且不在接受新的任务，真正的终止状态   &lt;br /&gt;    private static final int ST_TERMINATED = 5;   &lt;br /&gt;   &lt;br /&gt;    //优雅关闭的静默期   &lt;br /&gt;    private volatile long gracefulShutdownQuietPeriod;   &lt;br /&gt;    //优雅关闭超时时间   &lt;br /&gt;    private volatile long gracefulShutdownTimeout;   &lt;br /&gt;   &lt;br /&gt;    //Reactor的关闭Future   &lt;br /&gt;    private final Promise&amp;lt;?&amp;gt; terminationFuture = new DefaultPromise&amp;lt;Void&amp;gt;(GlobalEventExecutor.INSTANCE);   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public Future&amp;lt;?&amp;gt; shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {   &lt;br /&gt;   &lt;br /&gt;        ......省略参数校验.......   &lt;br /&gt;   &lt;br /&gt;        //此时Reactor的状态为ST_STARTED   &lt;br /&gt;        if (isShuttingDown()) {   &lt;br /&gt;            return terminationFuture();   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        boolean inEventLoop = inEventLoop();   &lt;br /&gt;        boolean wakeup;   &lt;br /&gt;        int oldState;   &lt;br /&gt;        for (;;) {   &lt;br /&gt;            if (isShuttingDown()) {   &lt;br /&gt;                return terminationFuture();   &lt;br /&gt;            }   &lt;br /&gt;            int newState;   &lt;br /&gt;            //需要唤醒Reactor去执行关闭流程   &lt;br /&gt;            wakeup = true;   &lt;br /&gt;            oldState = state;   &lt;br /&gt;            if (inEventLoop) {   &lt;br /&gt;                newState = ST_SHUTTING_DOWN;   &lt;br /&gt;            } else {   &lt;br /&gt;                switch (oldState) {   &lt;br /&gt;                    case ST_NOT_STARTED:   &lt;br /&gt;                    case ST_STARTED:   &lt;br /&gt;                        newState = ST_SHUTTING_DOWN;   &lt;br /&gt;                        break;   &lt;br /&gt;                    default:   &lt;br /&gt;                        //Reactor正在关闭或者已经关闭   &lt;br /&gt;                        newState = oldState;   &lt;br /&gt;                        wakeup = false;   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;            if (STATE_UPDATER.compareAndSet(this, oldState, newState)) {   &lt;br /&gt;                break;   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;        //优雅关闭静默期，在该时间内，用户还是可以向Reactor提交任务并且执行，只要有任务在Reactor中，就不能进行关闭   &lt;br /&gt;        //每隔100ms检测是否有任务提交进来，如果在静默期内没有新的任务提交，那么才会进行关闭 保证关闭行为的优雅   &lt;br /&gt;        gracefulShutdownQuietPeriod = unit.toNanos(quietPeriod);   &lt;br /&gt;        //优雅关闭的最大超时时间，优雅关闭行为不能超过该时间，如果超过的话 不管当前是否还有任务 都要进行关闭   &lt;br /&gt;        //保证关闭行为的可控   &lt;br /&gt;        gracefulShutdownTimeout = unit.toNanos(timeout);   &lt;br /&gt;   &lt;br /&gt;        //这里需要保证Reactor线程是在运行状态，如果已经停止，那么就不在进行后续关闭行为，直接返回terminationFuture   &lt;br /&gt;        if (ensureThreadStarted(oldState)) {   &lt;br /&gt;            return terminationFuture;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        //将正在监听IO事件的Reactor从Selector上唤醒，表示要关闭了，开始执行关闭流程   &lt;br /&gt;        if (wakeup) {   &lt;br /&gt;            //确保Reactor线程在执行完任务之后 不会在selector上停留   &lt;br /&gt;            taskQueue.offer(WAKEUP_TASK);   &lt;br /&gt;            if (!addTaskWakesUp) {   &lt;br /&gt;                //如果此时Reactor正在Selector上阻塞，则可以确保Reactor被及时唤醒   &lt;br /&gt;                wakeup(inEventLoop);   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        return terminationFuture();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public Future&amp;lt;?&amp;gt; terminationFuture() {   &lt;br /&gt;        return terminationFuture;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;首先在开启关闭流程之前，需要调用 isShuttingDown() 判断一下当前 Reactor 是否已经开始关闭流程或者已经完成关闭。如果已经开始关闭了，这里会直接返回 Reactor 的 terminationFuture 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public boolean isShuttingDown() {   &lt;br /&gt;        return state &amp;gt;= ST_SHUTTING_DOWN;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;剩下的逻辑就是不停的在一个 for 循环中通过 CAS 不停的尝试将 Reactor 的当前 ST_STARTED 状态改为 ST_SHUTTING_DOWN 正在关闭状态。&lt;/p&gt; &lt;p&gt;如果通过 inEventLoop() 判断出当前执行线程是 Reactor 线程，那么表示当前 Reactor 的状态只会是 ST_STARTED 运行状态，那么就可以直接将 newState 设置为 ST_SHUTTING_DOWN 。因为只有 Reactor 处于 ST_STARTED 状态的时候才会运行到这里。否则在前边就直接返回 terminationFuture了。&lt;/p&gt; &lt;p&gt;如果当前执行线程为用户线程并不是 Reactor 线程的话，那么此时 Reactor 的状态可能是正在关闭状态或者已经关闭状态，用户线程在重复发起 Reactor 的关闭流程。所以这些异常场景的处理会在 switch(oldState){....} 语句中完成。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;            switch (oldState) {   &lt;br /&gt;                    case ST_NOT_STARTED:   &lt;br /&gt;                    case ST_STARTED:   &lt;br /&gt;                        newState = ST_SHUTTING_DOWN;   &lt;br /&gt;                        break;   &lt;br /&gt;                    default:   &lt;br /&gt;                        //Reactor正在关闭或者已经关闭   &lt;br /&gt;                        newState = oldState;   &lt;br /&gt;                        //当前Reactor已经处于关闭流程中，则无需在唤醒Reactor了   &lt;br /&gt;                        wakeup = false;   &lt;br /&gt;                }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果当前 Reactor 还未发起关闭流程，比如状态为 ST_NOT_STARTED 或者 ST_STARTED ，那么直接可以放心的将 newState 设置为 ST_SHUTTING_DOWN 。&lt;/p&gt; &lt;p&gt;如果当前 Reactor 已经处于关闭流程中或者已经完成关闭，比如状态为 ST_SHUTTING_DOWN ，ST_SHUTDOWN 或者 ST_TERMINATED 。则没有必要在唤醒 Reactor 重复执行关闭流程了 wakeup = false。Reactor 的状态维持当前状态不变。&lt;/p&gt; &lt;p&gt;当 Reactor 的状态确定完毕后，则在 for 循环中不断的通过 CAS 修改 Reactor 的当前状态。此时 oldState = ST_STARTED ，newState = ST_SHUTTING_DOWN 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;          if (STATE_UPDATER.compareAndSet(this, oldState, newState)) {   &lt;br /&gt;                break;   &lt;br /&gt;            }   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;随后在 Reactor 中设置我们在《6.1 ReactorGroup 的优雅谢幕》小节开始处介绍的控制 Netty 优雅关闭的两个非常重要的核心参数：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;gracefulShutdownQuietPeriod&lt;/code&gt;：优雅关闭静默期，默认为 2s 。当 Reactor 中已经没有异步任务需要在执行时，该静默期开始触发，Netty 在这里会每隔    &lt;code&gt;100ms&lt;/code&gt;检测一下是否有任务提交进来，如果在静默期内没有新的任务提交，那么才会进行关闭，保证关闭行为的优雅。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;gracefulShutdownTimeout&lt;/code&gt;：优雅关闭超时时间，默认为 15s 。优雅关闭行为不能超过该时间，如果超过的话不管当前是否还有任务都要进行关闭，保证关闭行为的可控。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;流程走到这里，Reactor 就开始准备执行关闭流程了，那么在进行关闭操作之前，我们需要确保 Reactor 线程此时应该是运行状态，如果此时 Reactor 线程还未开始运行那么就需要让它运行起来执行关闭操作。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;        //这里需要保证Reactor线程是在运行状态，如果已经停止，   &lt;br /&gt;        //那么就不在进行后续关闭行为，直接返回terminationFuture   &lt;br /&gt;        if (ensureThreadStarted(oldState)) {   &lt;br /&gt;            return terminationFuture;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;    private boolean ensureThreadStarted(int oldState) {   &lt;br /&gt;        if (oldState == ST_NOT_STARTED) {   &lt;br /&gt;            try {   &lt;br /&gt;                doStartThread();   &lt;br /&gt;            } catch (Throwable cause) {   &lt;br /&gt;                STATE_UPDATER.set(this, ST_TERMINATED);   &lt;br /&gt;                terminationFuture.tryFailure(cause);   &lt;br /&gt;   &lt;br /&gt;                if (!(cause instanceof Exception)) {   &lt;br /&gt;                    // Also rethrow as it may be an OOME for example   &lt;br /&gt;                    PlatformDependent.throwException(cause);   &lt;br /&gt;                }   &lt;br /&gt;                return true;   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;        return false;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果此时 Reactor 线程刚刚执行完异步任务或者正在 Selector 上阻塞，那么我们需要确保 Reactor 线程被及时的唤醒，从而可以直接进入关闭流程。wakeup == true。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;这里的 addTaskWakesUp 默认为 false 。表示并不是只有 addTask 方法才能唤醒 Reactor 线程 还有其他方法可以唤醒 Reactor 线程，比如 SingleThreadEventExecutor#execute 方法还有本小节介绍的 SingleThreadEventExecutor#shutdownGracefully 方法都会唤醒 Reactor 线程。&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;关于 addTaskWakesUp 字段的详细含义和作用，大家可以回顾下   &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484087&amp;idx=1&amp;sn=0c065780e0f05c23c8e6465ede86cba0&amp;chksm=ce77c4f0f9004de63be369a664105708bc5975b52993f4a6df223caed34cc1ef6185a16acd75&amp;token=1670680185&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《一文聊透 Netty 核心引擎 Reactor 的运转架构》&lt;/a&gt;一文中的《1.2.2 Reactor 开始轮询 IO 就绪事件》小节。&lt;/p&gt;&lt;/blockquote&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;     //将正在监听IO事件的Reactor从Selector上唤醒，表示要关闭了，开始执行关闭流程   &lt;br /&gt;        if (wakeup) {   &lt;br /&gt;            //确保Reactor线程在执行完任务之后 不会在selector上停留   &lt;br /&gt;            taskQueue.offer(WAKEUP_TASK);   &lt;br /&gt;            if (!addTaskWakesUp) {   &lt;br /&gt;                //如果此时Reactor正在Selector上阻塞，则可以确保Reactor被及时唤醒   &lt;br /&gt;                wakeup(inEventLoop);   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;通过    &lt;code&gt;taskQueue.offer(WAKEUP_TASK)&lt;/code&gt;向 Reactor 中添加 WAKEUP_TASK，可以确保 Reactor 在执行完异步任务之后不会在 Selector 上做停留，直接执行关闭操作。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;如果此时 Reactor 线程正在 Selector 上阻塞，那么直接调用 wakeup(inEventLoop) 唤醒 Reactor 线程，直接来到关闭流程。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;public final class NioEventLoop extends SingleThreadEventLoop {   &lt;br /&gt;    @Override   &lt;br /&gt;    protected void wakeup(boolean inEventLoop) {   &lt;br /&gt;        if (!inEventLoop &amp;amp;&amp;amp; nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {   &lt;br /&gt;            selector.wakeup();   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;6.3 Reactor 线程的优雅关闭&lt;/h3&gt; &lt;p&gt;我们先来通过一张 Reactor 优雅关闭整体流程图来从总体上俯撼一下关闭流程：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Reactor线程优雅关闭流程.png &lt;p&gt;通过  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484087&amp;idx=1&amp;sn=0c065780e0f05c23c8e6465ede86cba0&amp;chksm=ce77c4f0f9004de63be369a664105708bc5975b52993f4a6df223caed34cc1ef6185a16acd75&amp;scene=21#wechat_redirect"&gt;《一文聊透Netty核心引擎Reactor的运转架构》&lt;/a&gt;一文的介绍，我们知道 Reacto r是在一个 for 循环中 996 不停地处理 IO 事件以及执行异步任务。如下面笔者提取的 Reactor 运行框架所示：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public final class NioEventLoop extends SingleThreadEventLoop {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    protected void run() {   &lt;br /&gt;        for (;;) {   &lt;br /&gt;            try {   &lt;br /&gt;                  .......1.监听Channel上的IO事件.......   &lt;br /&gt;                  .......2.处理Channel上的IO事件.......   &lt;br /&gt;                  .......3.执行异步任务..........   &lt;br /&gt;            } finally {   &lt;br /&gt;                try {   &lt;br /&gt;                    if (isShuttingDown()) {   &lt;br /&gt;                        //关闭Reactor上注册的所有Channel,停止处理IO事件，触发unActive以及unRegister事件   &lt;br /&gt;                        closeAll();   &lt;br /&gt;                        //注销掉所有Channel停止处理IO事件之后，剩下的就需要执行Reactor中剩余的异步任务了   &lt;br /&gt;                        if (confirmShutdown()) {   &lt;br /&gt;                            return;   &lt;br /&gt;                        }   &lt;br /&gt;                    }   &lt;br /&gt;                } catch (Error e) {   &lt;br /&gt;                    throw (Error) e;   &lt;br /&gt;                } catch (Throwable t) {   &lt;br /&gt;                    handleLoopException(t);   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在 Reactor 在每次 for 循环的末尾 finally{....} 语句块中都会通过 isShuttingDown() 方法去检查当前 Reactor 的状态是否是关闭状态，如果是关闭状态则开始正式进入 Reactor 的优雅关闭流程。&lt;/p&gt; &lt;p&gt;我们在本文前边《1.2 优雅关闭》小节中在讨论优雅关闭方案的时候提到，我们要着重从以下两个方面来实施优雅关闭：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;首先需要切走程序承担的现有流量。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;保证现有剩余的任务可以执行完毕，保证业务无损。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;Netty 这里实现的优雅关闭同样也遵从这两个要点。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;在优雅关闭流程开始之前首先会调用 closeAll() 方法，将 Reactor 上注册的所有 Channel 全部关闭掉，切掉现有流量。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;随后会调用 confirmShutdown() 方法，将剩余的异步任务执行完毕。在该方法中只要有异步任务需要执行，就不能关闭，保证业务无损。该方法返回值为 true 时表示可以进行关闭。返回 false 时表示不能马上关闭。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;6.3.1 切走流量&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;    private void closeAll() {   &lt;br /&gt;        //这里的目的是清理selector中的一些无效key   &lt;br /&gt;        selectAgain();   &lt;br /&gt;        //获取Selector上注册的所有Channel   &lt;br /&gt;        Set&amp;lt;SelectionKey&amp;gt; keys = selector.keys();   &lt;br /&gt;        Collection&amp;lt;AbstractNioChannel&amp;gt; channels = new ArrayList&amp;lt;AbstractNioChannel&amp;gt;(keys.size());   &lt;br /&gt;        for (SelectionKey k: keys) {   &lt;br /&gt;            //获取NioSocketChannel   &lt;br /&gt;            Object a = k.attachment();   &lt;br /&gt;            if (a instanceof AbstractNioChannel) {   &lt;br /&gt;                channels.add((AbstractNioChannel) a);   &lt;br /&gt;            } else {   &lt;br /&gt;                .........省略......   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        for (AbstractNioChannel ch: channels) {   &lt;br /&gt;            //关闭Reactor上注册的所有Channel，并在pipeline中触发unActive事件和unRegister事件   &lt;br /&gt;            ch.unsafe().close(ch.unsafe().voidPromise());   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;首先会通过 selectAgain() 最后一次在 Selector 上执行一次非阻塞轮询操作，目的是清除 Selector 上的一些无效 Key 。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;关于无效 Key 的清除，详细细节大家可以回看下   &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484087&amp;idx=1&amp;sn=0c065780e0f05c23c8e6465ede86cba0&amp;chksm=ce77c4f0f9004de63be369a664105708bc5975b52993f4a6df223caed34cc1ef6185a16acd75&amp;token=1670680185&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《一文聊透Netty核心引擎Reactor的运转架构》&lt;/a&gt;一文中的《3.1.3 从Selector中移除失效的SelectionKey》小节。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;随后通过 selector.keys() 获取在 Selector 上注册的所有 SelectionKey 。进而获取到 Netty 中的 NioSocketChannel 。SelectionKey 与 NioSocketChannel 的对应关系如下图所示：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;channel与SelectionKey对应关系.png &lt;p&gt;最后将注册在 Reactor 上的这些 NioSocketChannel 挨个进行关闭。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;Channel 的关闭流程可以回看下笔者的这篇文章   &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247485060&amp;idx=1&amp;sn=736360af6eb3a4db496de2d6665ebd3c&amp;chksm=ce77c0c3f90049d5e44692c2cf837e8d85bb28758b243d505c43c48ce703da1edadfc19360b1&amp;token=1978355368&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《且看 Netty 如何应对 TCP 连接的正常关闭，异常关闭，半关闭场景》&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;6.3.2 保证业务无损&lt;/h3&gt; &lt;p&gt;该方法中的逻辑是保证 Reactor 进行优雅关闭的核心，Netty 这里为了保证业务无损，采取的是只要有异步任务 Task 或者 ShutdwonHooks 需要执行，就不能关闭，需要等待所有 tasks 或者 ShutdownHooks 执行完毕，才会考虑关闭的事情。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;    protected boolean confirmShutdown() {   &lt;br /&gt;        if (!isShuttingDown()) {   &lt;br /&gt;            return false;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        if (!inEventLoop()) {   &lt;br /&gt;            throw new IllegalStateException(&amp;quot;must be invoked from an event loop&amp;quot;);   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        //取消掉所有的定时任务   &lt;br /&gt;        cancelScheduledTasks();   &lt;br /&gt;   &lt;br /&gt;        if (gracefulShutdownStartTime == 0) {   &lt;br /&gt;            //获取优雅关闭开始时间，相对时间   &lt;br /&gt;            gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        //这里判断只要有task任务需要执行就不能关闭   &lt;br /&gt;        if (runAllTasks() || runShutdownHooks()) {   &lt;br /&gt;            if (isShutdown()) {   &lt;br /&gt;                // Executor shut down - no new tasks anymore.   &lt;br /&gt;                return true;   &lt;br /&gt;            }   &lt;br /&gt;   &lt;br /&gt;            /**   &lt;br /&gt;             * gracefulShutdownQuietPeriod表示在这段时间内，用户还是可以继续提交异步任务的，Reactor在这段时间内   &lt;br /&gt;             * 是会保证这些任务被执行到的。   &lt;br /&gt;             *   &lt;br /&gt;             * gracefulShutdownQuietPeriod = 0 表示 没有这段静默时期，当前Reactor中的任务执行完毕后，无需等待静默期，执行关闭   &lt;br /&gt;             * */   &lt;br /&gt;            if (gracefulShutdownQuietPeriod == 0) {   &lt;br /&gt;                return true;   &lt;br /&gt;            }   &lt;br /&gt;            //避免Reactor在Selector上阻塞，因为此时已经不会再去处理IO事件了，专心处理关闭流程   &lt;br /&gt;            taskQueue.offer(WAKEUP_TASK);   &lt;br /&gt;            return false;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        //此时Reactor中已经没有任务可执行了，是时候考虑关闭的事情了   &lt;br /&gt;        final long nanoTime = ScheduledFutureTask.nanoTime();   &lt;br /&gt;   &lt;br /&gt;        //当Reactor中所有的任务执行完毕后，判断是否超过gracefulShutdownTimeout   &lt;br /&gt;        //如果超过了 则直接关闭   &lt;br /&gt;        if (isShutdown() || nanoTime - gracefulShutdownStartTime &amp;gt; gracefulShutdownTimeout) {   &lt;br /&gt;            return true;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        //即使现在没有任务也还是不能进行关闭，需要等待一个静默期，在静默期内如果没有新的任务提交，才会进行关闭   &lt;br /&gt;        //如果在静默期内还有任务继续提交，那么静默期将会重新开始计算，进入一轮新的静默期检测   &lt;br /&gt;        if (nanoTime - lastExecutionTime &amp;lt;= gracefulShutdownQuietPeriod) {   &lt;br /&gt;            taskQueue.offer(WAKEUP_TASK);   &lt;br /&gt;            try {   &lt;br /&gt;                //gracefulShutdownQuietPeriod内每隔100ms检测一下 是否有任务需要执行   &lt;br /&gt;                Thread.sleep(100);   &lt;br /&gt;            } catch (InterruptedException e) {   &lt;br /&gt;                // Ignore   &lt;br /&gt;            }   &lt;br /&gt;   &lt;br /&gt;            return false;   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        // 在整个gracefulShutdownQuietPeriod期间内没有任务需要执行或者静默期结束 则无需等待gracefulShutdownTimeout超时，直接关闭   &lt;br /&gt;        return true;   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在关闭流程开始之前，Netty 首先会调用 cancelScheduledTasks() 方法将 Reactor 中剩余需要执行的定时任务全部取消掉。&lt;/p&gt; &lt;p&gt;记录优雅关闭开始时间 gracefulShutdownStartTime ，这是为了后续判断优雅关闭流程是否超时。&lt;/p&gt; &lt;p&gt;调用 runAllTasks() 方法将 Reactor 中 TaskQueue 里剩余的异步任务全部取出执行。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;运行剩余tasks和hooks.png &lt;p&gt;调用 runShutdownHooks() 方法将用户注册在 Reactor 上的 ShutdownHook 取出执行。&lt;/p&gt; &lt;p&gt;我们可以在用户线程中通过如下方式向 Reactor 中注册 ShutdownHooks ：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;        NioEventLoop reactor = (NioEventLoop) ctx.channel().eventLoop();   &lt;br /&gt;        reactor.addShutdownHook(new Runnable() {   &lt;br /&gt;            @Override   &lt;br /&gt;            public void run() {   &lt;br /&gt;                .....关闭逻辑....   &lt;br /&gt;            }   &lt;br /&gt;        });   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在 Reactor 进行关闭的时候，会取出用户注册的这些 ShutdownHooks 进行运行。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {   &lt;br /&gt;   &lt;br /&gt;   //可以向Reactor添加shutdownHook，当Reactor关闭的时候会被调用   &lt;br /&gt;   private final Set&amp;lt;Runnable&amp;gt; shutdownHooks = new LinkedHashSet&amp;lt;Runnable&amp;gt;();   &lt;br /&gt;   &lt;br /&gt;   private boolean runShutdownHooks() {   &lt;br /&gt;        boolean ran = false;   &lt;br /&gt;        while (!shutdownHooks.isEmpty()) {   &lt;br /&gt;            List&amp;lt;Runnable&amp;gt; copy = new ArrayList&amp;lt;Runnable&amp;gt;(shutdownHooks);   &lt;br /&gt;            shutdownHooks.clear();   &lt;br /&gt;            for (Runnable task: copy) {   &lt;br /&gt;                try {   &lt;br /&gt;                    //Reactor线程挨个顺序同步执行   &lt;br /&gt;                    task.run();   &lt;br /&gt;                } catch (Throwable t) {   &lt;br /&gt;                    logger.warn(&amp;quot;Shutdown hook raised an exception.&amp;quot;, t);   &lt;br /&gt;                } finally {   &lt;br /&gt;                    ran = true;   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        if (ran) {   &lt;br /&gt;            lastExecutionTime = ScheduledFutureTask.nanoTime();   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        return ran;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt;  &lt;p&gt;需要注意的是这里的 ShutdownHooks 是 Netty 提供的一种机制并不是我们在《3. JVM 中的 ShutdownHook》小节中介绍的 JVM 中的 ShutdownHooks 。&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;JVM 中的 ShutdownHooks 是一个 Thread ，JVM 在关闭之前会   &lt;strong&gt;并发无序&lt;/strong&gt;地运行。而 Netty 中的 ShutdownHooks 是一个 Runnable ，Reactor 在关闭之前，会由 Reactor 线程   &lt;strong&gt;同步有序&lt;/strong&gt;地执行。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;strong&gt;这里需要注意的是只要有 tasks 和 hooks 需要执行 Netty 就会一直执行下去直到这些任务全部执行完为止&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;当 Reactor 没有任何任务需要执行时，这时就会判断当前关闭流程所用时间是否超过了我们前边设定的优雅关闭最大超时时间 gracefulShutdownTimeout 。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;nanoTime - gracefulShutdownStartTime &amp;gt; gracefulShutdownTimeout   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果关闭流程因为前边这些任务的执行导致已经超时，那么就直接关闭 Reactor ，退出 Reactor 的工作循环。&lt;/p&gt; &lt;p&gt;如果没有超时，那么这时就会触发前边介绍的优雅关闭的静默期 gracefulShutdownQuietPeriod 。&lt;/p&gt; &lt;p&gt;在静默期中 Reactor 线程会每隔 100ms 检查一下是否有用户提交任务请求，如果有的话，就需要保证将用户提交的这些任务执行完毕。然后静默期将会重新开始计算，进入一轮新的静默期检测。&lt;/p&gt; &lt;p&gt;如果在整个静默期内，没有任何任务提交，则无需等待 gracefulShutdownTimeout 超时，直接关闭 Reactor ，退出 Reactor 的工作循环。&lt;/p&gt; &lt;p&gt;从以上过程我们可以看出 Netty 的优雅关闭至少需要等待一个静默期的时间。还有一点是 Netty 优雅关闭的时间可能会超出 gracefulShutdownTimeout ，因为 Netty 需要保证遗留剩余的任务被执行完毕。当所有任务执行完毕之后，才会去检测是否超时。&lt;/p&gt; &lt;h2&gt;6.4 Reactor 的最终关闭流程&lt;/h2&gt; &lt;p&gt;当在静默期内没有任何任务提交或者关闭流程超时时，上小节中介绍的 confirmShutdown() 就会返回 true 。随即 Reactor 线程就会退出工作循环。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public final class NioEventLoop extends SingleThreadEventLoop {   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    protected void run() {   &lt;br /&gt;        for (;;) {   &lt;br /&gt;            try {   &lt;br /&gt;                  .......1.监听Channel上的IO事件.......   &lt;br /&gt;                  .......2.处理Channel上的IO事件.......   &lt;br /&gt;                  .......3.执行异步任务..........   &lt;br /&gt;            } finally {   &lt;br /&gt;                try {   &lt;br /&gt;                    if (isShuttingDown()) {   &lt;br /&gt;                        //关闭Reactor上注册的所有Channel,停止处理IO事件，触发unActive以及unRegister事件   &lt;br /&gt;                        closeAll();   &lt;br /&gt;                        //注销掉所有Channel停止处理IO事件之后，剩下的就需要执行Reactor中剩余的异步任务了   &lt;br /&gt;                        if (confirmShutdown()) {   &lt;br /&gt;                            return;   &lt;br /&gt;                        }   &lt;br /&gt;                    }   &lt;br /&gt;                } catch (Error e) {   &lt;br /&gt;                    throw (Error) e;   &lt;br /&gt;                } catch (Throwable t) {   &lt;br /&gt;                    handleLoopException(t);   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们在  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247484005&amp;idx=1&amp;sn=52f51269902a58f40d33208421109bc3&amp;chksm=ce77c422f9004d340e5b385ef6ba24dfba1f802076ace80ad6390e934173a10401e64e13eaeb&amp;token=1670680185&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《详细图解 Netty Reactor 启动全流程》&lt;/a&gt;一文中的《1.3.3 Reactor 线程的启动》小节中的介绍中提到，Reactor 线程的启动是通过第一个异步任务被提交到 Reactor 中的时候被触发的。在向 Reactor 提交任务的方法  &lt;code&gt;SingleThreadEventExecutor#execute(java.lang.Runnable, boolean)&lt;/code&gt;中会触发下面 doStartThread() 方法的调用，在这里会调用前边提到的 Reactor 工作循环 run() 方法。&lt;/p&gt; &lt;p&gt;在 doStartThread() 方法的 finally{...} 语句块中会完成 Reactor 的最终关闭流程，也就是 Reactor 在退出 run 方法中的 for 循环之后的后续收尾流程。&lt;/p&gt; &lt;p&gt;最终 Reactor 的优雅关闭完整流程如下图所示：&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Reactor优雅关闭全流程.png &lt;pre&gt;  &lt;code&gt;public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {   &lt;br /&gt;   &lt;br /&gt;    private void doStartThread() {   &lt;br /&gt;        assert thread == null;   &lt;br /&gt;        executor.execute(new Runnable() {   &lt;br /&gt;            @Override   &lt;br /&gt;            public void run() {   &lt;br /&gt;   &lt;br /&gt;                ..........省略.........   &lt;br /&gt;   &lt;br /&gt;                try {   &lt;br /&gt;                    //Reactor线程开始轮询处理IO事件，执行异步任务   &lt;br /&gt;                    SingleThreadEventExecutor.this.run();   &lt;br /&gt;                    //后面的逻辑为用户调用shutdownGracefully关闭Reactor退出循环 走到这里   &lt;br /&gt;                    success = true;   &lt;br /&gt;                } catch (Throwable t) {   &lt;br /&gt;                    logger.warn(&amp;quot;Unexpected exception from an event executor: &amp;quot;, t);   &lt;br /&gt;                } finally {   &lt;br /&gt;                    //走到这里表示在静默期内已经没有用户在向Reactor提交任务了，或者达到优雅关闭超时时间，开始对Reactor进行关闭   &lt;br /&gt;                    //如果当前Reactor不是关闭状态则将Reactor的状态设置为ST_SHUTTING_DOWN   &lt;br /&gt;                    for (;;) {   &lt;br /&gt;                        int oldState = state;   &lt;br /&gt;                        if (oldState &amp;gt;= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(   &lt;br /&gt;                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {   &lt;br /&gt;                            break;   &lt;br /&gt;                        }   &lt;br /&gt;                    }   &lt;br /&gt;   &lt;br /&gt;                    try {   &lt;br /&gt;                        for (;;) {   &lt;br /&gt;                            //此时Reactor线程虽然已经退出，而此时Reactor的状态为shuttingdown，但任务队列还在   &lt;br /&gt;                            //用户在此时依然可以提交任务，这里是确保用户在最后的这一刻提交的任务可以得到执行。   &lt;br /&gt;                            if (confirmShutdown()) {   &lt;br /&gt;                                break;   &lt;br /&gt;                            }   &lt;br /&gt;                        }   &lt;br /&gt;   &lt;br /&gt;                        for (;;) {   &lt;br /&gt;                            // 当Reactor的状态被更新为SHUTDOWN后，用户提交的任务将会被拒绝   &lt;br /&gt;                            int oldState = state;   &lt;br /&gt;                            if (oldState &amp;gt;= ST_SHUTDOWN || STATE_UPDATER.compareAndSet(   &lt;br /&gt;                                    SingleThreadEventExecutor.this, oldState, ST_SHUTDOWN)) {   &lt;br /&gt;                                break;   &lt;br /&gt;                            }   &lt;br /&gt;                        }   &lt;br /&gt;   &lt;br /&gt;                        // 这里Reactor的状态已经变为SHUTDOWN了，不会在接受用户提交的新任务了   &lt;br /&gt;                        // 但为了防止用户在状态变为SHUTDOWN之前，也就是Reactor在SHUTTINGDOWN的时候 提交了任务   &lt;br /&gt;                        // 所以此时Reactor中可能还会有任务，需要将剩余的任务执行完毕   &lt;br /&gt;                        confirmShutdown();   &lt;br /&gt;                    } finally {   &lt;br /&gt;                        try {   &lt;br /&gt;                            //SHUTDOWN状态下，在将全部的剩余任务执行完毕后，则将Selector关闭   &lt;br /&gt;                            cleanup();   &lt;br /&gt;                        } finally {   &lt;br /&gt;                            // 清理Reactor线程中的threadLocal缓存，并通知相应future。   &lt;br /&gt;                            FastThreadLocal.removeAll();   &lt;br /&gt;   &lt;br /&gt;                            //ST_TERMINATED状态为Reactor真正的终止状态   &lt;br /&gt;                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);   &lt;br /&gt;                               &lt;br /&gt;                            //使得awaitTermination方法返回   &lt;br /&gt;                            threadLock.countDown();   &lt;br /&gt;   &lt;br /&gt;                            //统计一下当前reactor任务队列中还有多少未执行的任务，打出日志   &lt;br /&gt;                            int numUserTasks = drainTasks();   &lt;br /&gt;                            if (numUserTasks &amp;gt; 0 &amp;amp;&amp;amp; logger.isWarnEnabled()) {   &lt;br /&gt;                                logger.warn(&amp;quot;An event executor terminated with &amp;quot; +   &lt;br /&gt;                                        &amp;quot;non-empty task queue (&amp;quot; + numUserTasks + &amp;apos;)&amp;apos;);   &lt;br /&gt;                            }   &lt;br /&gt;   &lt;br /&gt;                            /**   &lt;br /&gt;                             * 通知Reactor的terminationFuture成功，在创建Reactor的时候会向其terminationFuture添加Listener   &lt;br /&gt;                             * 在listener中增加terminatedChildren个数，当所有Reactor关闭后 ReactorGroup关闭成功   &lt;br /&gt;                             * */   &lt;br /&gt;                            terminationFuture.setSuccess(null);   &lt;br /&gt;                        }   &lt;br /&gt;                    }   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        });   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;流程走到 doStartThread 方法中的 finally{...} 语句块中的时候，这个时候表示在优雅关闭的静默期内，已经没有任务继续向 Reactor 提交了。或者关闭耗时已经超过了设定的优雅关闭最大超时时间。&lt;/p&gt; &lt;p&gt;现在正式来到了 Reactor 的关闭流程。在流程开始之前需要确保当前 Reactor 的状态为 ST_SHUTTING_DOWN 正在关闭状态。&lt;/p&gt; &lt;p&gt;注意此刻用户线程依然可以向 Reactor 提交任务。当 Reactor 的状态变为 ST_SHUTDOWN 或者 ST_TERMINATED 时，用户向 Reactor 提交的任务就会被拒绝，但是此时 Reactor 的状态为 ST_SHUTTING_DOWN ，依然可以接受用户提交过来的任务。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {   &lt;br /&gt;  @Override   &lt;br /&gt;  public boolean isShutdown() {   &lt;br /&gt;        return state &amp;gt;= ST_SHUTDOWN;   &lt;br /&gt;  }   &lt;br /&gt;   &lt;br /&gt;  private void execute(Runnable task, boolean immediate) {   &lt;br /&gt;        boolean inEventLoop = inEventLoop();   &lt;br /&gt;        addTask(task);   &lt;br /&gt;        if (!inEventLoop) {   &lt;br /&gt;            startThread();   &lt;br /&gt;            //当Reactor的状态为ST_SHUTDOWN时，拒绝用户提交的异步任务，但是在优雅关闭ST_SHUTTING_DOWN状态时还是可以接受用户提交的任务的   &lt;br /&gt;            if (isShutdown()) {   &lt;br /&gt;                boolean reject = false;   &lt;br /&gt;                try {   &lt;br /&gt;                    if (removeTask(task)) {   &lt;br /&gt;                        reject = true;   &lt;br /&gt;                    }   &lt;br /&gt;                } catch (UnsupportedOperationException e) {   &lt;br /&gt;                }   &lt;br /&gt;                if (reject) {   &lt;br /&gt;                    reject();   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        .........省略........   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;所以 Reactor 从工作循环 run 方法中退出随后流程一路走到这里来的这段时间，用户仍然有可能向 Reactor 提交任务，为了确保关闭流程的优雅，这里会在 for 循环中不停的执行 confirmShutdown() 方法直到所有的任务全部执行完毕。&lt;/p&gt; &lt;p&gt;随后会将 Reactor 的状态改为 ST_SHUTDOWN 状态，此时用户就不能在向 Reactor 提交任务了。如果此时在提交任务就会收到 RejectedExecutionException 异常。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;大家这里可能会有疑问，Netty 在 Reactor 的状态变为 ST_SHUTDOWN 之后，又一次调用了 confirmShutdown() 方法，这是为什么呢？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;其实这样做的目的是为了防止 Reactor 状态在变为 SHUTDOWN 之前，在这个极限的时间里，用户又向 Reactor 提交了任务，所以还需要最后一次调用 confirmShutdown() 将在这个极限时间内提交的任务执行完毕。&lt;/p&gt; &lt;p&gt;以上逻辑步骤就是真正优雅关闭的精髓所在，确保任务全部执行完毕，保证业务无损。&lt;/p&gt; &lt;p&gt;在我们优雅处理流程介绍完了之后，下面就是关闭 Reactor 的流程了：&lt;/p&gt; &lt;p&gt;Reactor 会在 SHUTDOWN 状态下，将 Selector 进行关闭。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;    @Override   &lt;br /&gt;    protected void cleanup() {   &lt;br /&gt;        try {   &lt;br /&gt;            selector.close();   &lt;br /&gt;        } catch (IOException e) {   &lt;br /&gt;            logger.warn(&amp;quot;Failed to close a selector.&amp;quot;, e);   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;清理 Reactor 线程中遗留的所有 ThreadLocal 缓存。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;FastThreadLocal.removeAll();   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;将 Reactor 的状态由 SHUTDOWN 改为 ST_TERMINATED 状态。  &lt;strong&gt;此时 Reactor 就算真正的关闭了&lt;/strong&gt;。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt; STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;用户线程可能会调用 Reactor 的 awaitTermination 方法阻塞等待 Reactor 的关闭，当 Reactor 关闭之后会调用 threadLock.countDown() 使得用户线程从 awaitTermination 方法返回。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {   &lt;br /&gt;   &lt;br /&gt;    private final CountDownLatch threadLock = new CountDownLatch(1);   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {   &lt;br /&gt;           &lt;br /&gt;         ........省略.......   &lt;br /&gt;   &lt;br /&gt;        //等待Reactor关闭   &lt;br /&gt;        threadLock.await(timeout, unit);   &lt;br /&gt;        return isTerminated();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public boolean isTerminated() {   &lt;br /&gt;        return state == ST_TERMINATED;   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当这一切处理完毕之后，最后就会设置 Reactor 的 terminationFuture 为 success 。此时注册在 Reactor 的 terminationFuture 上的 listener 就会被回调。&lt;/p&gt; &lt;p&gt;这里还记得我们在  &lt;a href="https://mp.weixin.qq.com/s?__biz=Mzg2MzU3Mjc3Ng==&amp;mid=2247483907&amp;idx=1&amp;sn=084c470a8fe6234c2c9461b5f713ff30&amp;chksm=ce77c444f9004d52e7c6244bee83479070effb0bc59236df071f4d62e91e25f01715fca53696&amp;token=1470157323&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;《Reactor 在 Netty 中的实现(创建篇)》&lt;/a&gt;一文中介绍的，在 ReactorGroup 中的所有 Reactor 被挨个全部创建成功之后，会向所有 Reactor 的 terminationFuture 注册一个 terminationListener 。&lt;/p&gt; &lt;p&gt;在 terminationListener 中检测当前 ReactorGroup 中的所有 Reactor 是否全部完成关闭，如果已经全部关闭，则设置 ReactorGroup 的 terminationFuture 为Success。此刻 ReactorGroup 关闭流程结束，Netty 正式优雅谢幕完毕~~&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   &lt;br /&gt;public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {   &lt;br /&gt;   &lt;br /&gt;    //Reactor线程组中的Reactor集合   &lt;br /&gt;    private final EventExecutor[] children;   &lt;br /&gt;    //记录关闭的Reactor个数，当Reactor全部关闭后，才可以认为关闭成功   &lt;br /&gt;    private final AtomicInteger terminatedChildren = new AtomicInteger();   &lt;br /&gt;    //ReactorGroup关闭future   &lt;br /&gt;    private final Promise&amp;lt;?&amp;gt; terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);   &lt;br /&gt;   &lt;br /&gt;    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,   &lt;br /&gt;                                            EventExecutorChooserFactory chooserFactory, Object... args) {   &lt;br /&gt;         &lt;br /&gt;        ........挨个创建Reactor........   &lt;br /&gt;   &lt;br /&gt;        final FutureListener&amp;lt;Object&amp;gt; terminationListener = new FutureListener&amp;lt;Object&amp;gt;() {   &lt;br /&gt;            @Override   &lt;br /&gt;            public void operationComplete(Future&amp;lt;Object&amp;gt; future) throws Exception {   &lt;br /&gt;                if (terminatedChildren.incrementAndGet() == children.length) {   &lt;br /&gt;                    //当所有Reactor关闭后 才认为是关闭成功   &lt;br /&gt;                    terminationFuture.setSuccess(null);   &lt;br /&gt;                }   &lt;br /&gt;            }   &lt;br /&gt;        };   &lt;br /&gt;   &lt;br /&gt;        for (EventExecutor e: children) {   &lt;br /&gt;            e.terminationFuture().addListener(terminationListener);   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        ........省略........   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;到现在为止，Netty 的整个优雅关闭流程，笔者就为大家详细介绍完了，下图为整个优雅关闭的完整流程图，大家可以对照下面这副总体流程图在回顾下我们前面介绍的源码逻辑。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Reactor优雅关闭总流程.png &lt;h3&gt;6.5 Reactor 的状态变更流转&lt;/h3&gt; &lt;p&gt;在本文的最后，笔者再来带着大家回顾下 Reactor 的状态变更流程。&lt;/p&gt; &lt;img&gt;&lt;/img&gt;Reactor的状态变更.png &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;在 Reactor 被创建出来之后状态为 ST_NOT_STARTED。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;随着第一个异步任务的提交 Reactor 开始启动随后状态为 ST_STARTED 。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;当调用 shutdownGracefully 方法之后，Reactor 的状态变为 ST_SHUTTING_DOWN 。表示正在进行优雅关闭。此时用户仍可向 Reactor 提交异步任务。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;当 Reactor 中遗留的任务全部执行完毕之后，Reactor 的状态变为 ST_SHUTDOWN 。此时如果用户继续向 Reactor 提交异步任务，会被拒绝，并收到 RejectedExecutionException 异常。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;当 Selector 完成关闭，并清理掉 Reactor 线程中所有的 TheadLocal 缓存之后，Reactor 的状态变为 ST_TERMINATED 。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;到这里关于优雅关闭的前世今生笔者就位大家全部交代完毕了，信息量比较大，需要好好消化一下，很佩服大家能够一口气看到这里。&lt;/p&gt; &lt;p&gt;本文我们从进程优雅启停方案开始聊起，以优雅关闭的实现方案为起点，先是介绍了优雅关闭的底层基石-内核的信号量机制，从内核又聊到了 JVM 的 ShutdownHook 原理以及执行过程，最后通过三个知名的开源框架为案例，分别从 Spring 的优雅关闭机制聊到了 Dubbo 的优雅关闭，最后通过 Dubbo 的优雅关闭引出了 Netty 优雅关闭的详细实现方案，前后呼应。&lt;/p&gt; &lt;p&gt;好了，本文的内容就到这里了，大家辛苦了，相信大家认真看完之后一定会收获很大，我们下篇文章见~~~&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62377-java-%E6%8A%80%E6%9C%AF-%E4%B8%AD%E9%97%B4%E4%BB%B6</guid>
      <pubDate>Mon, 22 Aug 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>程序员要进入Google、Amazon这样的顶级IT公司，需要达到什么样的技术水平？</title>
      <link>https://itindex.net/detail/62365-%E7%A8%8B%E5%BA%8F%E5%91%98-google-amazon</link>
      <description>&lt;div&gt;  &lt;p&gt;现在北美求职市场飘忽不定。能不能进入这些公司或毕业就上车，在一定程度上更取决于你的就业时间，而不是你的能力。我当年在USC见过很多水人赶上Amazon扩招直接两轮OA拿Offer的，也有很厉害的大神赶上市场缩招半年没有一个面试，被ICC拒了的。2020年疫情北美缩招，我在国内阿里的一个同学所在的组新招的应届生都是UCLA, UT-Austin，CMU 正统MSCS毕业， 在北美没找到工作的。&lt;/p&gt;  &lt;p&gt;北美互联网就业远比你想象的更具有不确定性，难度也远比你想象的大的多。身为过来人，我给你们的诚恳并且稳妥一点的建议是。 1.先拿到国内一线互联网公司的Offer在国内攒两三年工作经验。2. 一定要练英语口语，至少达到能不看字幕听懂老友记的水准。3. 申请一个北美两年制的硕士，尽量能在两年内发一篇CCF B类以上的论文，这能证明你在相关的领域达到一定的见解。4. 刷题刷题再刷题，尽量刷到800道以上，并且真正理解。&lt;/p&gt;  &lt;p&gt;如果你能达到以上四点，即便遇到就业低峰期，你也是有可能在北美的就业市场分到一杯羹的。否则按照当前的形式，你留学两年的钱很有可能打了水漂。&lt;/p&gt;  &lt;p&gt;最后劝各位不要听一些求职公司或者所谓润学人的胡言乱语，这群人的目标根本不是为了帮你就业，而是掏干你口袋里的钱让你报名他们的求职班等等。北美这些一线公司现在的门槛远比他们描述的高的多的多。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>zhihu</category>
      <guid isPermaLink="true">https://itindex.net/detail/62365-%E7%A8%8B%E5%BA%8F%E5%91%98-google-amazon</guid>
      <pubDate>Mon, 15 Aug 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>数据库内核的快照技术实现原理 - 吴祖洋的技术博客</title>
      <link>https://itindex.net/detail/62291-%E6%95%B0%E6%8D%AE%E5%BA%93-%E5%86%85%E6%A0%B8-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;div&gt;    &lt;p&gt;&amp;quot;快照(Snapshot)&amp;quot;是数据库领域非常重要的一个概念, 最初是用于数据备份. 如今, 快照技术已经成为数据库内核(引擎)最核心的技术特性之一. 数据库内核的绝大多数操作, 都依赖于快照, 例如,      &lt;a href="https://www.ideawu.net/blog/tag/leveldb"&gt;LevelDB&lt;/a&gt;的每一次读取操作和遍历操作, 其内部都必须创建一个快照, 所以, 对于一个请求量非常大的系统, 数据库内核每秒种就要创建和销毁几十万次快照. 因此, 如何快速地创建和销毁快照, 成为一个数据库内核(引擎)必须要解决的问题.&lt;/p&gt;    &lt;p&gt;本文从源头出发, 逐步推演, 探讨数据库内核是如何实现快照技术的. 数据库内核创建快照, 将使用如下技术:&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;全量拷贝(Full Clone)&lt;/li&gt;      &lt;li&gt;写时拷贝(Copy On Write)&lt;/li&gt;      &lt;li&gt;分区拷贝(Partitioning)&lt;/li&gt;      &lt;li&gt;多版本(Multi Versioning, Leveling, Zero Copy)&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;无论何种实现快照的技术, 数据      &lt;strong&gt;拷贝&lt;/strong&gt;都不可避免, 差异点主要是不同的技术拷贝的数据量不同, 以及拷贝的数据含义不同(直接拷贝和间接拷贝). 直接拷贝意味着拷贝数据本身, 间接拷贝则拷贝数据的&amp;quot;指针&amp;quot;.&lt;/p&gt;    &lt;p&gt;拷贝意味着      &lt;strong&gt;互斥, 独占, 加锁&lt;/strong&gt;, 因为拷贝需要保证数据的完整性. 硬盘数据拷贝速度是较慢的, 往往被认为不可接受, 应极力避免. 内存数据拷贝速度较快, 但仍然需要减少拷贝的数据量.      &lt;br /&gt;&lt;/p&gt;    &lt;h3&gt;1: 全量拷贝(Full Clone)&lt;/h3&gt;    &lt;p&gt;全量拷贝技术是创建快照最本能的实现方法, 也是最低效的方法. 因为数据量往往非常多, 而且还常常在硬盘上, 拷贝成本高, 耗时长. 在进行全量拷贝的过程中, 需要排斥写操作, 所以数据库系统是无法提供写服务的.&lt;/p&gt;    &lt;h3&gt;2: 写时拷贝(Copy On Write)&lt;/h3&gt;    &lt;p&gt;全量拷贝低效速度慢, 那么, 一个可能的优化方向是在某些不需要拷贝的场景不做任何拷贝. 在某些场景, 例如读请求, 虽然需要创建一个快照并且去读快照, 但是, 读操作并不会修改快照数据和原来的数据, 如果这时整个系统也正好没有任何写操作请求, 那么, 就没有必要做全量拷贝.&lt;/p&gt;    &lt;p&gt;同时, 为了应对某个时刻收到写操作请求, 需要随时做准备, 一旦有写操作请求时, 再做一次全量拷贝. 写时拷贝技术是一种      &lt;strong&gt;概率优化&lt;/strong&gt;技术, 不像纯朴想法所认为地去优化全量拷贝本身的性能.&lt;/p&gt;    &lt;p&gt;概率优化是一种      &lt;strong&gt;外部优化&lt;/strong&gt;, 依赖于实际使用场景的概率分布, 在遇到 bad cases 时, 因为内部依然非常慢, 所以在 bad cases 场景不起作用. 所以, 仅仅写时拷贝技术并不能从根本上解决问题.&lt;/p&gt;    &lt;p&gt;写时拷贝的关键是引入一个      &lt;strong&gt;单点标记&lt;/strong&gt;和额外的一步      &lt;strong&gt;必要操作路径(单点)&lt;/strong&gt;, 所有的请求都必须走这条路径, 这样才能截获写操作请求, 在写操作请求之前进行数据拷贝.&lt;/p&gt;    &lt;h3&gt;3: 分区拷贝(Partitioning)&lt;/h3&gt;    &lt;p&gt;分区(Partitioning)技术是计算机领域非常重要的技术思想, 有点像&amp;quot;分而治之&amp;quot;思想, 和&amp;quot;并发&amp;quot;技术是统一的. 因为无论针对快照或是原始数据的修改, 在操作过程期间往往只修改很小的一个比例, 例如一个拥有一百万行记录的数据库表, 在创建完快照和销毁快照这段时间内, 也许只修改了两三行记录, 没有必要拷贝整个表.&lt;/p&gt;    &lt;p&gt;分区拷贝将数据拆分为多个部分(Partition), 结合 Copy On Write 技术, 在有必要的时候, 才拷贝被修改的那部分数据. Partitioning 技术在计算机领域应用非常广泛, 像我们常说的&amp;quot;减小锁粒度&amp;quot;, &amp;quot;分布式数据库 Sharding&amp;quot;, &amp;quot;并发&amp;quot;等等, 这些都是 Partitioning.&lt;/p&gt;    &lt;h3&gt;4: 多版本(Multi Versioning)&lt;/h3&gt;    &lt;p&gt;      &lt;a href="https://www.ideawu.net/blog/archives/1143.html"&gt;多版本&lt;/a&gt;技术抛弃了纯朴观念里的&amp;quot;修改&amp;quot;一词, 当想要修改某项数据时, 只是简单地写新数据写到其它地方, 暂时不管旧数据是怎么样的. 然后, 引入一个成本极小的标记, 修改数据的指向(也即将旧数据标记为作废, 将新数据标记为有效). 这个标记和前文介绍写时拷贝技术时提到的&amp;quot;必要路径&amp;quot;是一个意思.&lt;/p&gt;    &lt;p&gt;基于多版本技术, 创建快照时不再拷贝任何原始数据(Zero Copy), 只有成本极小的对标记的修改操作, 所以, 无论数据是在内存还是在极慢的硬盘里, 都不影响创建快照的速度. 如果这个标记是放在内存中的, 那么, 针对1TB的数据库每秒创建百万个快照也没有问题.&lt;/p&gt;    &lt;p&gt;不过, 多版本技术也有缺点. 它虽然不影响创建快照的速度, 也很少影响写操作的速度, 但是, 它严重影响读操作的性能, 因为我们必须读取全部的版本出来, 才能知道哪个版本是我们需要的, 版本越多, 性能就越差. 所以, 使用多版本技术时, 都要结合      &lt;strong&gt;垃圾回收(GC)&lt;/strong&gt;技术, 尽快删除不需要的版本.&lt;/p&gt;    &lt;p&gt;和分区技术不同, 多版本技术的不同版本是指同一个对象, 不是独立的, 可以把版本理解为层(Level), 最终多个层合并(Merge), 形成最终的对象数据. 而分区是独立的, 不需要合并, 只需要连接(Chain)起来.&lt;/p&gt;    &lt;p&gt;在实践中, 多版本技术往往要通过扩大数据对象的粒度来减少版本数量.&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62291-%E6%95%B0%E6%8D%AE%E5%BA%93-%E5%86%85%E6%A0%B8-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Sun, 05 Jun 2022 21:04:18 CST</pubDate>
    </item>
    <item>
      <title>【得物技术】看完这篇异地多活的改造，我决定和架构师battle一下</title>
      <link>https://itindex.net/detail/62289-%E6%8A%80%E6%9C%AF-%E6%9E%B6%E6%9E%84%E5%B8%88-battle</link>
      <description>&lt;h1&gt;简述&lt;/h1&gt;
 &lt;p&gt;异地多活的概念以及为什么要做异地多活这里就不进行概述了。概念性的很多，像什么同城双活、两地三中心、三地五中心等等概念。如果有对这些容灾架构模式感兴趣的可以阅读下这篇文章进行了解：  &lt;a href="https://mp.weixin.qq.com/s/zPwhnO9ECVua0ER0c8lYAA"&gt;《浅谈业务级灾备的架构模式》&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;阅读本篇文章之前，我们先明确一下背景，这样大家后续在看的时候就不会产生困惑。&lt;/p&gt;
 &lt;h2&gt;1.1 机房划分&lt;/h2&gt;
 &lt;p&gt;得物多活改造一期目前有两个机房，分别是机房A和机房B。文章中大部分图中都会有标识，这就说明是两个不同的机房。&lt;/p&gt;
 &lt;p&gt;A机房我们定义为中心机房，也就是多活上线之前正在使用的机房。如果说到中心机房那指的就是A机房。另一个B机房，在描述的时候可能会说成单元机房，那指的就是B机房。&lt;/p&gt;
 &lt;h2&gt;1.2 单元化&lt;/h2&gt;
 &lt;p&gt;单元化简单点我们直接就可以认为是一个机房，在这个单元内能够完成业务的闭环。比如说用户进入APP，浏览商品，选择商品确认订单，下单，支付，查看订单信息，这整个流程都在一个单元中能够完成，并且数据也是存储在这个单元里面。&lt;/p&gt;
 &lt;p&gt;做单元化无非就两个原因，容灾和提高系统并发能力。但是也得考虑机房建设的规模和技术，硬件等投入的成本。具体的就不多讲了，大家大概理解了就行。&lt;/p&gt;
 &lt;h1&gt;2. 改造点&lt;/h1&gt;
 &lt;p&gt;了解改造点之前我们先来看下目前单机房的现状是什么样子，才能更好的帮助大家去理解为什么要做这些改造。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1718c1ff173a4a96aa93b62007400d12~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如上图所示，客户端的请求进来会先到SLB(负载均衡)，然后到我们内部的网关，通过网关再分发到具体的业务服务。业务服务会依赖Redis, Mysql, MQ, Nacos等中间件。&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/1e0976eb53474ceeb030eac406441db3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;大家看上面这张图可能会感觉很简单，其实也就是一些常用的中间件，再多一个机房部署罢了，这有什么难度。如果你这样想我只能说一句：  &lt;strong&gt;格局小了啊&lt;/strong&gt;。&lt;/p&gt;
 &lt;h2&gt;2.1 流量调度&lt;/h2&gt;
 &lt;p&gt;用户的请求，从客户端发出，这个用户的请求该到哪个机房，这是我们要改造的第一个点。&lt;/p&gt;
 &lt;p&gt;没做多活之前，域名会解析到一个机房内，做了多活后，域名会随机解析到不同的机房中。如果按照这种随机的方式是肯定有问题的，对于服务的调用是无所谓的，因为没有状态。但是服务内部依赖的存储是有状态的呀。&lt;/p&gt;
 &lt;p&gt;我们是电商业务，用户在中心机房下了一个单，然后跳转到订单详情，这个时候请求到了单元机房，底层数据同步有延迟，一访问报个错：订单不存在。 用户当场就懵了，钱都付了，订单没了。&lt;/p&gt;
 &lt;p&gt;所以针对同一个用户，尽可能在一个机房内完成业务闭环。为了解决流量调度的问题，我们基于OpenResty二次开发出了DLB流量网关，DLB会对接多活控制中心，能够知道当前访问的用户是属于哪个机房，如果用户不属于当前机房，DLB会直接将请求路由到该用户所属机房内的DLB。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5d7dbcd07ce471396c9b5409572e529~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果每次都随机到固定的机房，再通过DLB去校正，必然会存在跨机房请求，耗时加长。所以在这块我们也是结合客户端做了一些优化，在DLB校正请求后，我们会将用户对应的机房IP直接通过Header响应给客户端。这样下次请求的时候，客户端就可以直接通过这个IP访问。&lt;/p&gt;
 &lt;p&gt;如果用户当前访问的机房挂了，客户端需要降级成之前的域名访问方式，通过DNS解析到存活的机房。&lt;/p&gt;
 &lt;h2&gt;2.2 RPC框架&lt;/h2&gt;
 &lt;p&gt;当用户的请求达到了单元机房内，理论上后续所有的操作都是在单元机房完成。前面我们也提到了，用户的请求尽量在一个机房内完成闭环，只是尽量，没有说全部。&lt;/p&gt;
 &lt;p&gt;这是因为有的业务场景不适合划分单元，比如库存扣减。所以在我们的划分里面，有一个机房是中心机房，那些不做多活的业务只会部署在中心机房里面，那么库存扣减的时候就需要跨机房调用。&lt;/p&gt;
 &lt;p&gt;请求在中心机房，怎么知道单元机房的服务信息？所以我们的注册中心（Nacos）要做双向同步，这样才能拿到所有机房的服务信息。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/546a433f92004704b1c8fbbd51ffa228~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们的注册信息采用双向复制后，对于中心服务，直接跨机房调用。对于单元服务会存在多个机房的服务信息，如果不进行控制，则会出现调用其他机房的情况，所以RPC框架要进行改造。&lt;/p&gt;
 &lt;h3&gt;2.2.1 定义路由类型&lt;/h3&gt;
 &lt;ol&gt;
  &lt;li&gt;默认路由&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;请求到中心机房，会优先调用中心机房内的服务，如果中心机房无此服务，则调用单元机房的服务，如果单元机房没有此服务则直接报错。&lt;/p&gt;
 &lt;ol start="2"&gt;
  &lt;li&gt;单元路由&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;请求到单元机房，那么说明此用户的流量规则是在单元机房，接下来所有的RPC调用都只会调用单元机房内的服务，没有服务则报错。&lt;/p&gt;
 &lt;ol start="3"&gt;
  &lt;li&gt;中心路由&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;请求到单元机房，那么直接调用中心机房的服务，中心机房没有服务则报错。请求到中心机房，那么就本机房调用。&lt;/p&gt;
 &lt;h3&gt;2.2.2 业务改造&lt;/h3&gt;
 &lt;p&gt;业务方需要对自己的接口（Java interface）进行标记是什么类型，通过@HARoute加在接口上面。标记完成后，在Dubbo接口进行注册的时候，会把路由类型放入到这个接口的元数据里面，在Nacos后台可以查看。后面通过RPC调用接口内部所有的方法都会按照标记类型进行路由。&lt;/p&gt;
 &lt;p&gt;如果标记为单元路由，目前我们内部的规范是方法的第一个参数为小写的long buyerId，RPC在路由的时候会根据这个值判断用户所在的机房。&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/5a3abea8a56e4aaa8df8b28fc506cd7b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.2.3 改造过程&lt;/h3&gt;
 &lt;ol&gt;
  &lt;li&gt;接口复制一份，命名为UnitApi，第一个参数加long buyerId。在新接口的实现里面调用老接口，新旧接口共存。&lt;/li&gt;
  &lt;li&gt;将UnitApi发布上线，此时没有流量。&lt;/li&gt;
  &lt;li&gt;业务方需要升级其他域的API包，将老接口的调用切换为新的UnitApi，此处增加开关控制。&lt;/li&gt;
  &lt;li&gt;上线后，通过开关控制调用走UnitApi，有问题可关闭开关。&lt;/li&gt;
  &lt;li&gt;下线老的API，完成切换。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h3&gt;2.2.4 遇到的问题&lt;/h3&gt;
 &lt;h4&gt;2.2.4.1 其他场景切单元接口&lt;/h4&gt;
 &lt;p&gt;除了RPC直接调用的接口，还有一大部分是通过Dubbo泛化过来的，这块在上线后也需要将流量切到UnitApi，等老接口没有请求量之后才能下线。&lt;/p&gt;
 &lt;h4&gt;2.2.4.2 接口分类&lt;/h4&gt;
 &lt;p&gt;接口进行分类，之前没有多活的约束，一个Java interface中的方法可能各种各样，如果现在你的interface为单元路由，那么里面的方法第一个参数都必须加buyerId，其他没有buyerId场景的方法要挪出去。&lt;/p&gt;
 &lt;h4&gt;2.2.4.3 业务层面调整&lt;/h4&gt;
 &lt;p&gt;业务层面调整，比如之前查询订单只需要一个订单号，但是现在需要buyerId进行路由，所以接入这个接口的上游都需要调整。&lt;/p&gt;
 &lt;h2&gt;2.3 数据库&lt;/h2&gt;
 &lt;p&gt;请求顺利的到达了服务层，接下来要跟数据库打交道了。数据库我们定义了不同的类型，定义如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;单元化&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;此库为单元库，会同时在两个机房部署，每个机房都有完整的数据，数据采用双向同步。&lt;/p&gt;
 &lt;ol start="2"&gt;
  &lt;li&gt;中心化&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;此库为中心库，只会在中心机房部署。&lt;/p&gt;
 &lt;ol start="3"&gt;
  &lt;li&gt;中心单元化&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;此库为中心单元库，会同时在两个机房部署，中心可以读写，其他机房只能读。中心写数据后单向复制到另一个机房。&lt;/p&gt;
 &lt;h3&gt;2.3.1 代理中间件&lt;/h3&gt;
 &lt;p&gt;目前各个业务方用的都是客户端形式的Sharding中间件，每个业务方的版本还不一致。在多活切流的过程中需要对数据库禁写来保证业务数据的准确性，如果没有统一的中间件，这将是一件很麻烦的事情。&lt;/p&gt;
 &lt;p&gt;所以我们通过对ShardingSphere进行深度定制，二次开发数据库代理中间件 彩虹桥。各业务方需要接入彩虹桥来替换之前的Sharding方式。在切换过程中，如何保证稳定平滑迁移，出问题如何快速恢复，我们也有一套成功的实践，大家可以看下我之前写的这篇文章  &lt;a href="https://mp.weixin.qq.com/s/mYWKF0Ipso4GqSLmF4C3tg"&gt;《客户端分片到Proxy分片，如丝般顺滑的平稳迁移》&lt;/a&gt;，里面有实现方式。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5487d40d452b46ca8a9a338f460df736~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.3.2 分布式ID&lt;/h3&gt;
 &lt;p&gt;单元化的库，数据层面会做双向同步复制操作。如果直接用表的自增ID则会出现下面的冲突问题：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c2f09c79745b40b7bf32661e4b13dd18~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个问题可以通过设置不同机房id有不同的自增步长来解决，但比较麻烦，后续可能会增加更多的机房。我们采用了一种一劳永逸的方式，接入全局唯一的分布式ID来避免主键的冲突。&lt;/p&gt;
 &lt;h4&gt;2.3.2.1 客户端接入&lt;/h4&gt;
 &lt;p&gt;目前，接入分布式ID有两种方式，一种是应用内通过基础架构提供的jar包接入，具体逻辑如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53ba27937d1d425db18c1a38943150a7~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;2.3.2.2 彩虹桥接入&lt;/h4&gt;
 &lt;p&gt;另一种就是在彩虹桥中对具体的表配置ID的生成方式，支持对接分布式ID服务。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1fe47340de264b6cbf822116babc6bbc~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.3.3 业务改造&lt;/h3&gt;
 &lt;h4&gt;2.3.3.1 单元化库写请求必须携带ShardingKey&lt;/h4&gt;
 &lt;p&gt;在Dao层对表进行操作的时候，会通过ThreadLocal设置当前方法的ShardingKey，然后通过Mybatis拦截器机制，将ShardingKey通过Hint的方式放入SQL中，带给彩虹桥。彩虹桥会判断当前的ShardingKey是否属于当前机房，如果不是直接禁写报错。&lt;/p&gt;
 &lt;p&gt;这里跟大家简单的说明下为什么切流过程中要禁写，这个其实跟JVM的垃圾回收有点相似。如果不对操作禁写，那么就会不断的产生数据，而我们切流，一定要保证当前机房的数据全部同步过去了之后才开始生效流量规则，否则用户切到另一个机房，数据没同步完，就会产生业务问题。除了彩虹桥会禁写，RPC框架内部也会根据流量规则进行阻断。&lt;/p&gt;
 &lt;h4&gt;2.3.3.2 数据库连接指定连接模式&lt;/h4&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/7cb5471266b3404da3aa0155323aaa16~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;这里解释下为什么要有连接模式这个&lt;/strong&gt;   &lt;strong&gt;设计&lt;/strong&gt;   &lt;strong&gt;？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在我们的项目中，会出现同时连接2个库的情况，一个单元库，一个中心库。如果没有连接模式，上层代码是一份，这个项目会在中心和单元两个机房同时部署，也就是两个地方都会去创建数据源。&lt;/p&gt;
 &lt;p&gt;但实际上，我的中心库只需要在中心机房连接就可以了，因为中心库所有的操作都是中心接口，流量必定会走中心，我在单元机房去连接是没有意义的。另一个问题就是我不需要在单元机房维护中心库的数据库信息，如果没有连接模式，那么单元机房的彩虹桥也必须要有中心库的信息，因为项目会进行连接。&lt;/p&gt;
 &lt;h3&gt;2.3.4 遇到的问题&lt;/h3&gt;
 &lt;h4&gt;2.3.4.1 单元接口中不能访问中心数据库&lt;/h4&gt;
 &lt;p&gt;如果接口标记成了单元接口，那么只能操作单元库。在以前没有做多活改造的时候，基本上没有什么中心和单元的概念，所有的表也都是放在一起的。多活改造后，我们会根据业务场景对数据库进行划分。&lt;/p&gt;
 &lt;p&gt;划分后，中心库只会被中心机房的程序使用，在单元机房是不允许连接中心库。所以单元接口里面如果涉及到对中心库的操作，必定会报错。这块需要调整成走中心的RPC接口。&lt;/p&gt;
 &lt;h4&gt;2.3.4.2 中心接口不能访问单元数据库&lt;/h4&gt;
 &lt;p&gt;跟上面同样的问题，如果接口是中心的，也不能在接口里面操作单元库。中心接口的请求都会强制走到中心机房，如果里面有涉及到另一个机房的操作，也必须走RPC接口进行正确的路由，因为你中心机房不能操作另一个机房的数据库。&lt;/p&gt;
 &lt;h4&gt;2.3.4.3 批量查询调整&lt;/h4&gt;
 &lt;p&gt;比如批量根据订单号进行查询，但是这些订单号不是同一个买家。如果随便用一个订单的买家作为路由参数，那么其他一些订单其实是属于另一个单元的，这样就有可能存在查询到旧数据的问题。&lt;/p&gt;
 &lt;p&gt;这样批量查询的场景，只能针对同一个买家可用，如果是不同的买家需要分批调用。&lt;/p&gt;
 &lt;h2&gt;2.4 Redis&lt;/h2&gt;
 &lt;p&gt;Redis在业务中用的比较多，在多活的改造中也有很多地方需要调整。对于Redis首先我们明确几个定义：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;不做双向同步&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Redis不会和数据库一样做双向同步，也就是中心机房一个Redis集群，单元机房一个Redis集群。每个机房的集群中只存在一部分用户的缓存数据，不是全量的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Redis类型&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Redis分为中心和单元，中心只会在中心机房部署，单元会在中心和单元两个机房部署。&lt;/p&gt;
 &lt;h3&gt;2.4.1 业务改造&lt;/h3&gt;
 &lt;h4&gt;2.4.1.1 Redis多数据源支持&lt;/h4&gt;
 &lt;p&gt;多活改造之前，每个应用都有一个单独的Redis集群，多活改造后，由于应用没有进行单元化和中心的拆分，所以一个应用中会存在需要连接两个Redis的情况。一个中心Redis，一个单元Redis。&lt;/p&gt;
 &lt;p&gt;基础架构提供的Redis包需要支持多数据源的创建，并且定义通用的配置格式，业务方只需要在自己 的配置里面指定集群和连接模式即可完成接入。此处的连接模式跟数据库的一致。&lt;/p&gt;
 &lt;p&gt;具体的Redis实例信息会在配置中心统一维护，不需要业务方关心，这样在做机房扩容的时候，业务方是不需要调整的，配置如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;spring.redis.sources.carts.mode=unit 
spring.redis.sources.carts.cluster-name=cartsCuster 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;同时我们在使用Redis的时候要指定对应的数据源，如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Autowired 
@Qualifier(RedisTemplateNameConstants.REDIS_TEMPLATE_UNIT) 
private RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate; 
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;2.4.1.2 数据一致性&lt;/h4&gt;
 &lt;p&gt;数据库缓存场景，由于Redis不会双向同步，就会存在数据的不一致性问题。比如用户一开始在中心机房，然后缓存了一份数据。进行切流，切到单元机房，单元机房又缓存了一份数据。再进行切回中心机房的操作，此时中心机房里的缓存是旧的数据，不是最新的数据。&lt;/p&gt;
 &lt;p&gt;所以在底层数据变更的时候，我们需要对缓存进行失效操作，这样才能保证数据的最终一致性。单纯依靠缓存的失效时间来达到一致性不是一个合适的方案。&lt;/p&gt;
 &lt;p&gt;这里我们的方案是采用订阅数据库的binlog来进行缓存的失效操作，可以订阅本机房的binlog，也可以订阅其他机房的binlog来实现所有机房的缓存失效。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c2c2367fc44e441f95e981ede11c0b72~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.4.2 遇到的问题&lt;/h3&gt;
 &lt;h4&gt;2.4.2.1 序列化协议兼容&lt;/h4&gt;
 &lt;p&gt;在接入新的Redis Client包后，测试环境出现了老数据的兼容问题。大部分应用都没问题，有个别应用虽然用了统一的底层包，但是自己定制了序列化方式，导致Redis按新的方式装配后没有用到自定义的协议，这块也是进行了改造，支持多数据源的协议自定义。&lt;/p&gt;
 &lt;h4&gt;2.4.2.2 分布式锁的使用&lt;/h4&gt;
 &lt;p&gt;目前项目中的分布式锁是基于Redis实现，当Redis有多个数据源之后，分布式锁也需要进行适配。在使用的地方要区分场景，默认都是用的中心Redis来加锁。&lt;/p&gt;
 &lt;p&gt;但是单元接口里面的操作都是买家场景，所以这部分需要调整为单元Redis锁对象进行加锁，这样能够提高性能。其他的一些场景有涉及到全局资源的锁定，那就用中心Redis锁对象进行加锁。&lt;/p&gt;
 &lt;h2&gt;2.5 RocketMQ&lt;/h2&gt;
 &lt;p&gt;请求到达服务层后，跟数据库和缓存都进行了交互，接下来的逻辑是要发一条消息出去，其他业务需要监听这个消息做一些业务处理。&lt;/p&gt;
 &lt;p&gt;如果是在单元机房发出的消息，发到了单元机房的MQ中，单元机房的程序进行消费，是没有问题的。但如果中心机房的程序要消费这个消息怎么办？所以MQ跟数据库一样，  &lt;strong&gt;也要做同步&lt;/strong&gt;，将消息同步到另一个机房的MQ中，至于另一个机房的消费者要不要消费，这就要让业务场景去决定。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2c69d28b56cf438f9e3b73b46633e2da~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.5.1 定义消费类型&lt;/h3&gt;
 &lt;h4&gt;2.5.1.1 中心订阅&lt;/h4&gt;
 &lt;p&gt;中心订阅指的是消息无论是在中心机房发出的还是单元机房发出的，都只会在中心机房进行消费。如果是单元机房发出的，会将单元的消息复制一份到中心进行消费。&lt;/p&gt;
 &lt;h4&gt;2.5.1.2 普通订阅&lt;/h4&gt;
 &lt;p&gt;普通订阅就是默认的行为，指的是就近消费。在中心机房发送的消息就由中心机房的消费者进行消费，在单元机房发送的消息就由单元机房的消费进行消费。&lt;/p&gt;
 &lt;h4&gt;2.5.1.3 单元订阅&lt;/h4&gt;
 &lt;p&gt;单元订阅指的是消息会根据ShardingKey进行消息的过滤，无论你在哪个机房发送消息，消息都会复制到另一个机房，此时两个机房都有该消息。通过ShardingKey判断当前消息应该被哪个机房消费，符合的才会进行消费，不符合的框架层面会自动ACK。&lt;/p&gt;
 &lt;h4&gt;2.5.1.4 全单元订阅&lt;/h4&gt;
 &lt;p&gt;全单元订阅指的是消息无论在哪个机房发出，都会在所有的机房进行消费。&lt;/p&gt;
 &lt;h3&gt;2.5.2 业务改造&lt;/h3&gt;
 &lt;h4&gt;2.5.2.1 消息发送方调整&lt;/h4&gt;
 &lt;p&gt;消息发送方，需要结合业务场景进行区分。如果是买家场景的业务消息，在发消息的时候需要将buyerId放入消息中，具体怎么消费由消费方决定。如果消费方是单元消费的话那么必须依赖发送方的buyerId，否则无法知道当前消息应该在哪个机房消费。&lt;/p&gt;
 &lt;h4&gt;2.5.2.2 消息消费方指定消费模式&lt;/h4&gt;
 &lt;p&gt;前面提到了中心订阅，单元订阅，普通订阅，全单元订阅多种模式，到底要怎么选就是要结合业务场景来定的，定好后在配置MQ信息的时候指定即可。&lt;/p&gt;
 &lt;p&gt;比如中心订阅就适合你整个服务都是中心的，其他机房都没部署，这个时候肯定适合中心订阅。比如你要对缓存进行清除，就比较适合全单元订阅，一旦数据有变更，所有机房的缓存都清除掉。&lt;/p&gt;
 &lt;h3&gt;2.5.3 遇到的问题&lt;/h3&gt;
 &lt;h4&gt;2.5.3.1 消息幂等消费&lt;/h4&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/a1acfad570344449826c85b57859b9c5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;用户在当前机房进行业务操作后，会产生消息。由于是单元订阅，所以会在当前机房进行消费。消费过程中，发生了切流操作，消费逻辑里面对数据库进行读写，但是单元表的操作都携带了ShardingKey，彩虹桥会判断ShardingKey是否符合当前的规则，发现不符合直接禁写报错。这批切流用户的消息就全部消费失败。等到流量切到另一个机房后，如果不进行消息的重新投递，那么这部分消息就丢失了，这就是为什么要复制到另一个机房进行消息的重新投递。&lt;/p&gt;
 &lt;h4&gt;2.5.3.2 切流场景的消息顺序问题&lt;/h4&gt;
 &lt;p&gt;上面讲到了在切流过程中，会将消息复制到另一个机房进行重新消费，然后是基于时间点去回放的，如果你的业务消息本身就是普通的Topic，在消息回放的时候如果同一个场景的消息有多条，这个顺序并不一定是按照之前的顺序来消费，所以这里涉及到一个消费顺序的问题。&lt;/p&gt;
 &lt;p&gt;如果你之前的业务场景本身就是用的顺序消息，那么是没问题的，如果之前不是顺序消息，这里就有可能有问题，我举个例子说明下：&lt;/p&gt;
 &lt;p&gt;有个业务场景，触发一次功能就会产生一条消息，这个消息是用户级别的，也就是一个用户会产生N条消息。消费方会消费这些消息进行存储，不是来一次消息就存储一条数据，而是同一个用户的只会存储一条，消息里面有个状态，会根据这个状态进行判断。&lt;/p&gt;
 &lt;p&gt;比如下面的消息总共投递了3条，按正常顺序消费最终的结果是status=valid。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;10:00:00  status=valid 
10:00:01  status=invalid 
10:00:02  status=valid 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果消息在另一个机房重新投递的时候，消费顺序变成了下面这样，最终结果就是status=invalid。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;10:00:00  status=valid 
10:00:02  status=valid 
10:00:01  status=invalid 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;解决方案有下面几种：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Topic换成顺序消息，以用户进行分区，这样就能保证每个用户的消息严格按照发送顺序进行消费&lt;/li&gt;
  &lt;li&gt;对消息做幂等，已消费过就不再消费。但是这里跟普通的消息不同，会有N条消息，如果对msgId进行存储，这样就可以判断是否消费过，但是这样存储压力太大，当然也可以只存储最近N条来减小存储压力。&lt;/li&gt;
  &lt;li&gt;消息幂等的优化方式，让消息发送方每发送一次，都带一个version，version必须是递增。消费方消费消息后把当前version存储起来，消费之前判断消息的version是否大于存储的version，满足条件才进行消费，这样既避免了存储的压力也能满足业务的需求。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;2.6 Job&lt;/h2&gt;
 &lt;p&gt;Job在我们这边用的不多，而且都是老的逻辑在用，只有几个凌晨统计数据的任务，新的都接入了我们自研的TOC(超时中心)来管理。&lt;/p&gt;
 &lt;h3&gt;2.6.1 业务改造&lt;/h3&gt;
 &lt;h4&gt;2.6.1.1 中心机房执行&lt;/h4&gt;
 &lt;p&gt;由于Job是老的一套体系，目前也只有个位数的任务在执行，所以在底层框架层面并没有支持多活的改造。后续会将Job的逻辑迁移到TOC中。&lt;/p&gt;
 &lt;p&gt;所以我们必须在业务层面进行改造来支持多活，改造方案有两种，分别介绍下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;两个机房同时执行Job，数据处理的时候，比如处理用户的数据，通过基础架构提供的能力，可以判断用户是否属于当前机房，如果数据就执行，否则就跳过这条数据。&lt;/li&gt;
  &lt;li&gt;从业务场景出发，Job都是凌晨去执行的，不属于在线业务，对数据一致性要求没那么高。即使不按单元化去处理数据，也没什么问题。所以只需要在中心机房执行Job即可，另一个机房我们可以通过配置让Job任务不进行生效。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;但是这种方式需要去梳理Job里的数据操作，如果有对中心库操作的，没关系，本身就是在中心机房跑。如果有对单元库操作的，需要调整为走RPC接口。&lt;/p&gt;
 &lt;h2&gt;2.7 TOC&lt;/h2&gt;
 &lt;p&gt;TOC是我们内部用的超时中心，当我们有需求需要在某个时间点进行触发业务动作的时候都可以接入超时中心来处理。&lt;/p&gt;
 &lt;p&gt;举个例子：订单创建后，N分钟内没有支付就自动取消。如果业务方自己实现，要么定时扫表进行处理，要么用MQ的延迟消息。有了TOC后，我们会在订单创建之后，往TOC注册一个超时任务，指定某个时间点，你要回调我。在回调的逻辑逻辑里去判断订单是否已完成支付，如果没有则取消。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28a22bff16be4c38811e96310350c9b3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.7.1 业务改造&lt;/h3&gt;
 &lt;h4&gt;2.7.1.1 任务注册调整&lt;/h4&gt;
 &lt;p&gt;在注册超时中心任务的时候，业务方需要识别任务是否要符合单元化的标准。如果此任务只是对中心数据库进行操作，那么这个任务回调在中心机房即可。如果此任务是对单元数据库操作，那么在注册任务的时候就需要指定buyerId，超时中心在触发回调的时候会根据buyerId进行路由到用户所属机房进行处理。&lt;/p&gt;
 &lt;p&gt;目前超时中心是只会在中心机房进行部署，也就是所有的任务都会在中心机房进行调度。如果任务注册的时候没有指定buyerId，超时中心在回调的时候就不知道要回调哪个机房，默认回调中心机房。要想让超时中心根据多活的路由规则进行回调，那么注册的时候必须指定buyerId。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aadf0a6c844744d78287000cec029df6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;3. 服务划分&lt;/h1&gt;
 &lt;p&gt;阅读完上面的改造内容，相信大家还有一个疑惑点就是我的服务该怎么划分呢？我要不要做单元化呢？&lt;/p&gt;
 &lt;h2&gt;3.1 整体方向&lt;/h2&gt;
 &lt;p&gt;首先要根据整个多活的一个整体目标和方向去梳理，比如我们的整体方向就是买家交易的核心链路必须实现单元化改造。那么这整个链路所有依赖的上下游都需要改造。&lt;/p&gt;
 &lt;p&gt;用户浏览商品，进入确认订单，下单，支付，查询订单信息。这个核心链路其实涉及到了很多的业务域，比如：商品，出价，订单，支付，商家等等。&lt;/p&gt;
 &lt;p&gt;在这些已经明确了的业务域下面，可能还有一些其他的业务域在支撑着，所以要把整体的链路都梳理出来，一起改造。当然也不是所有的都必须做单元化，还是得看业务场景，比如库存，肯定是在交易核心链路上，但是不需要改造，必须走中心。&lt;/p&gt;
 &lt;h2&gt;3.2 服务类型&lt;/h2&gt;
 &lt;h3&gt;3.2.1 中心服务&lt;/h3&gt;
 &lt;p&gt;中心服务只会在中心机房部署，并且数据库也一定是中心库。可以对整个应用进行打标成中心，这样外部访问这个服务的接口时都会被路由到中心机房。&lt;/p&gt;
 &lt;h3&gt;3.2.2 单元服务&lt;/h3&gt;
 &lt;p&gt;单元服务会在中心机房和单元机房同时部署，并且数据库也一定是单元库。单元服务是买家维度的业务，比如确认订单，下单。&lt;/p&gt;
 &lt;p&gt;买家维度的业务，在接口定义上，第一个参数必须是buyerId，因为要进行路由。用户的请求已经根据规则进行分流到不同的机房，只会操作对应机房里面的数据库。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d82a3ec9a4fb45b18507c9af91120ae5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;3.2.3 中心单元服务&lt;/h3&gt;
 &lt;p&gt;中心单元服务也就是说这个服务里面既有中心的接口也有单元的接口。并且数据库也是有两套。所以这种服务其实也是要在两个机房同时部署的，只不过是单元机房只会有单元接口过来的流量，中心接口是没有流量的。&lt;/p&gt;
 &lt;p&gt;一些底层的支撑业务，比如商品，商家这些就属于中心单元服务。支撑维度的业务是没有buyerId的，商品是通用的，并不属于某一个买家。&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/066f66e147804974a54d34b44e864827~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;从长远考虑，还是需要进行拆分，把中心的业务和单元的业务拆开，这样会比较清晰。对于后面新同学在定义接口，操作数据库，缓存等都有好处，因为现在是混合在一起的，你必须要知道当前这个接口的业务属于单元还是中心。&lt;/p&gt;
 &lt;p&gt;拆分也不是绝对的，还是那句话得从业务场景出发。像订单里面的买家和卖家的业务，我觉得可以拆分，后续维护也比较方便。但是像商品这种，并不存在两种角色，就是商品，对商品的增删改成在一个项目中也方便维护，只不过是要进行接口的分类，将新增，修改，删除的接口标记为中心。&lt;/p&gt;
 &lt;h1&gt;4. 切流方案&lt;/h1&gt;
 &lt;p&gt;前面我们也提到了再切流过程中，会禁写，会复制MQ的消息到另一个机房重新消费。接下来给大家介绍下我们的切流方案，能够帮助大家更深刻的理解整个多活的异常场景下处理流程。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d17f6959dcf4ee2ab941149c25238c6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;下发禁写规则&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;当需要切流的时候，操作人员会通过双活控制中心的后台进行操作。切流之前需要先进行已有流量的清理，需要下发禁写规则。禁写规则会下发到中心和单元两个机房对应的配置中心里面，通过配置中心去通知需要监听的程序。&lt;/p&gt;
 &lt;ol start="2"&gt;
  &lt;li&gt;彩虹桥执行禁写逻辑&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;彩虹桥会用到禁写规则，当禁写规则在配置中心修改后，彩虹桥能立马感知到，然后会根据SQL中携带的shardingkey进行规则的判断，看当前shardingkey是否属于这个机房，如果不属于则进行拦截。&lt;/p&gt;
 &lt;ol start="3"&gt;
  &lt;li&gt;反馈禁写生效结果&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;当配置变更后会推送到彩虹桥，配置中心会感知到配置推送的结果，然后将生效的结果反馈给双活控制中心。&lt;/p&gt;
 &lt;ol start="4"&gt;
  &lt;li&gt;推送禁写生效时间给Otter&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;双活控制中心收到所有的反馈后，会将全部生效的时间点通过MQ消息告诉Otter。&lt;/p&gt;
 &lt;ol start="5"&gt;
  &lt;li&gt;Otter进行数据同步&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;Otter收到消息会根据时间点进行数据同步。&lt;/p&gt;
 &lt;ol start="6"&gt;
  &lt;li&gt;Otter同步完成反馈同步结果&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;生效时间点之前的数据全部同步完成后会通过MQ消息反馈给双活控制中心。&lt;/p&gt;
 &lt;ol start="7"&gt;
  &lt;li&gt;下发最新流量规则&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;双活中心收到Otter的同步完成的反馈消息后，会下发流量规则，流量规则会下发到DLB,RPC,彩虹桥。&lt;/p&gt;
 &lt;p&gt;后续用户的请求就会直接被路由到正确的机房。&lt;/p&gt;
 &lt;h1&gt;5. 总结&lt;/h1&gt;
 &lt;p&gt;相信大家看了这篇文章，对多活的改造应该有了一定的了解。当然本篇文章并没有把所有多活相关的改造都解释清楚，因为整个改造的范围实在是太大了。本篇主要讲的是中间件层面和业务层面的一些改造点和过程，同时还有其他的一些点都没有提到。比如：机房网络的建设，发布系统支持多机房，监控系统支持多机房的整个链路监控，数据巡检的监控等等。&lt;/p&gt;
 &lt;p&gt;多活是一个高可用的容灾手段，但实现的成本和对技术团队的要求非常高。在实现多活的时候，我们应该结合业务场景去进行设计，不是所有系统，所有功能都要满足多活的条件，也没有100%的可用性，有的只是在极端场景下对业务的一些取舍罢了，优先保证核心功能。&lt;/p&gt;
 &lt;p&gt;以上就是得物订单域在参与多活改造中的一些经验，分享出来希望可以对正在阅读的你有一些帮助。&lt;/p&gt;
 &lt;p&gt;文/YINJIHUAN&lt;/p&gt;
 &lt;p&gt;关注得物技术，做最潮技术人！&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="bfb95c31910641bfad245de75f943c4f_tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/78f7122f87e248c9a6de1a0ee5c9f5f1~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&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/62289-%E6%8A%80%E6%9C%AF-%E6%9E%B6%E6%9E%84%E5%B8%88-battle</guid>
      <pubDate>Wed, 01 Jun 2022 17:23:59 CST</pubDate>
    </item>
    <item>
      <title>前端技术学习路线图</title>
      <link>https://itindex.net/detail/62280-%E5%89%8D%E7%AB%AF-%E6%8A%80%E6%9C%AF-%E5%AD%A6%E4%B9%A0</link>
      <description>&lt;p&gt;以下 Web 开发人员学习路线图是来自 Github   &lt;a href="https://github.com/kamranahmedse/developer-roadmap" rel="noopener" target="_blank"&gt;developer-roadmap&lt;/a&gt; 项目，目前已经有繁体版翻译   &lt;a href="https://github.com/goodjack/developer-roadmap-chinese" rel="noopener" target="_blank"&gt;developer-roadmap-chinese&lt;/a&gt;。&lt;/p&gt;

 &lt;p&gt;主要有三个方向，分别为前端开发、后端开发和运维。图片中不同颜色的意义：&lt;/p&gt;

 &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;黄色&lt;/strong&gt;：推荐；&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;灰色&lt;/strong&gt;：尽可能学习；&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;橙色&lt;/strong&gt;：任选其一。&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;可以看到，作为 Web 开发者，不管从事什么职位，下面这些技能点是必须掌握的：&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;Git，代码版本管理&lt;/li&gt;  &lt;li&gt;SSH；&lt;/li&gt;  &lt;li&gt;HTTP/HTTPs 和 API，想要真正成为一名 Web 开发者，对 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;li&gt;Github，Github（包括 Google、Stack Overflow）是 Web 开发者的宝矿，好好利用。&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://www.runoob.com/wp-content/uploads/2022/05/frontend.png"&gt;   &lt;img src="https://www.runoob.com/wp-content/uploads/2022/05/frontend.png"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;


 &lt;blockquote&gt;  &lt;p&gt;英文原始版本：   &lt;a href="https://roadmap.sh/frontend" rel="noopener" target="_blank"&gt;https://roadmap.sh/frontend&lt;/a&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/62280-%E5%89%8D%E7%AB%AF-%E6%8A%80%E6%9C%AF-%E5%AD%A6%E4%B9%A0</guid>
      <pubDate>Sat, 28 May 2022 15:02:12 CST</pubDate>
    </item>
    <item>
      <title>微前端框架核心技术揭秘</title>
      <link>https://itindex.net/detail/62164-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</link>
      <description>&lt;img alt="&amp;#24494;&amp;#21069;&amp;#31471;&amp;#26694;&amp;#26550;&amp;#26680;&amp;#24515;&amp;#25216;&amp;#26415;&amp;#25581;&amp;#31192;" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/mf-2.png"&gt;&lt;/img&gt;
 &lt;br /&gt;
 &lt;p&gt;2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”（Micro Frontend），其后该概念在web领域逐渐落地，在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义，带你走近微前端框架，揭秘那些“不为人知”的巧妙技术实现。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;概念&lt;/h2&gt;
 &lt;p&gt;什么是微前端呢？虽然它在2016年就被提出，但是直至今天，我们仍然只能描述它的轮廓，无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;微前端是一种类似于微服务的架构，它将微服务的理念应用于浏览器端，即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时，它们也可以进行并行开发。——《前端架构：从入门到微前端》&lt;/li&gt;
  &lt;li&gt;微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域，做他们关注和专注的事情。团队是跨职能的，从数据库到用户界面开发端到端的功能。——译，micro-frontends.org&lt;/li&gt;
  &lt;li&gt;微前端的核心价值在于 “技术栈无关”，这才是它诞生的理由，或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者，2020.11.20晚阿里云微前端线下沙龙&lt;/li&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;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;table border="0" cellpadding="0" cellspacing="0" width="598"&gt;

  &lt;tr&gt;
   &lt;td width="75"&gt;方案类型&lt;/td&gt;
   &lt;td width="107"&gt;典型技术&lt;/td&gt;
   &lt;td width="106"&gt;优点&lt;/td&gt;
   &lt;td width="160"&gt;缺点&lt;/td&gt;
   &lt;td width="148"&gt;共同点&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;接口协议&lt;/td&gt;
   &lt;td width="107"&gt;single-spa&lt;/td&gt;
   &lt;td width="106"&gt;比较自由，可自主封装&lt;/td&gt;
   &lt;td width="160"&gt;无法满足很多场景&lt;/td&gt;
   &lt;td height="102" rowspan="3" width="148"&gt;
    &lt;ul&gt;
     &lt;li&gt;子应用/模块互不干涉      &lt;br /&gt;
&lt;/li&gt;
     &lt;li&gt;技术栈无关&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;沙箱隔离&lt;/td&gt;
   &lt;td width="107"&gt;qiankun&lt;/td&gt;
   &lt;td width="106"&gt;开发思维简单直接&lt;/td&gt;
   &lt;td width="160"&gt;沙箱带来的性能等问题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;模块协议&lt;/td&gt;
   &lt;td width="107"&gt;webpack module federation&lt;/td&gt;
   &lt;td width="106"&gt;用模块思维理解引用&lt;/td&gt;
   &lt;td width="160"&gt;脱离构建工具无法使用&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;就目前市面上的情况而言，基于沙箱隔离的微前端方案占据了主导，也就是本文将要深入阐述的微前端框架们，也都是这类方案。其中原因，笔者认为最主要的一点，是基于沙箱隔离的方案可以让应用以最小的成本，从原本的单体大应用迁移到微前端架构上来。&lt;/p&gt;
 &lt;h2&gt;微前端框架对比评测&lt;/h2&gt;
 &lt;p&gt;微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎，市面上有非常多的微前端框架，笔者在2021年做过一次收集，比较有典型意义。（虽然在那之后还出现了新的微前端框架，但其大部分原理一致，因此，以下这些框架足以说明情况。）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/phodal/mooa"&gt;Mooa&lt;/a&gt;：基于Angular的微前端服务框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://single-spa.js.org/"&gt;Single-Spa&lt;/a&gt;：最早的微前端框架，兼容多种前端技术栈。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/umijs/qiankun"&gt;Qiankun&lt;/a&gt;：基于Single-Spa，阿里系开源微前端框架。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/ice-lab/icestark"&gt;Icestark&lt;/a&gt;：阿里飞冰微前端框架，兼容多种前端技术栈&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/aliyun/alibabacloud-console-os"&gt;console-os&lt;/a&gt; 是在阿里云控制台体系中孵化的微前端方案， 定位是面向企业级的微前端体系化解决方案。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;Module Federation&lt;/a&gt;：webpack给出的微前端方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/SAP/luigi"&gt;Luigi&lt;/a&gt;：一套复杂的分布式前端应用解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/frintjs/frint"&gt;FrintJS&lt;/a&gt;：自主解决依赖的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/puzzle-js/puzzle-js"&gt;PuzzleJS&lt;/a&gt;：一套复杂的前后端编译时相结合的微前端解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/worktile/ngx-planet"&gt;ngx-planet&lt;/a&gt;：基于angular的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://npmjs.com/package/mfy"&gt;麦饭（mfy）&lt;/a&gt;：精巧简易的微前端框架&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" rel="attachment wp-att-12266"&gt;   &lt;img alt="" height="537" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" width="1023"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除了webpack的联邦模块方案需要结合构建来做，比较特殊外，其他方案都是在运行时完成应用聚合。&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;a href="https://github.com/tangshuang/mfy"&gt;麦饭&lt;/a&gt;”也属于此类，因此，本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是  &lt;strong&gt;资源加载&lt;/strong&gt;和  &lt;strong&gt;环境隔离&lt;/strong&gt;两大问题，此外，还有路由、通信等问题。&lt;/p&gt;
 &lt;h3&gt;资源加载&lt;/h3&gt;
 &lt;p&gt;微前端框架需要从服务端拉取子应用的代码文件，并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案，现在常见的有两种方案，分别是：以JS文件作为入口；以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本，获得JS导出的内容，但是这样，仅能加载脚本资源，无法加载CSS等样式资源。而以HTML文件为入口，则可以通过HTML文件内的文件引用，把对应的所有JS、CSS文件都一起加载，而且，web站点都是以HTML文件作为入口，这也正好可以让子应用的开发者按照web开发的思路来写子应用。&lt;/p&gt;
 &lt;p&gt;笔者在写麦饭这个框架的时候，希望直接引入子应用就能跑，所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件，这个函数可以根据入口文件，解析子应用的全部资源，并做缓存。&lt;/p&gt;
 &lt;h4&gt;解析资源&lt;/h4&gt;
 &lt;p&gt;框架在获得HTML入口文件地址后，通过HTTP请求获得该文件的内容，对内容进行解析，解析时需要做资源树分析，也就是通过HTML读取所有资源文件，比如link, script[src]。在读取资源时，可能还需要读取资源本身又引入的资源。大致逻辑如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" rel="attachment wp-att-12267"&gt;   &lt;img alt="" height="172" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" width="801"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在解析过程中，还需要根据registerMicroApp（麦饭提供的注册接口）的配置，决定CSS rules怎么处理。解析获得CSS的技巧，是通过 &amp;lt;style&amp;gt;.sheet 读取 CSSStyleSheet 对象，从中抽离出所有CSS样式规则，再按配置逻辑生成最终的样式规则。&lt;/p&gt;
 &lt;h4&gt;预加载/懒加载&lt;/h4&gt;
 &lt;p&gt;在设计上，一个子应用的资源有两种可选加载形式。在麦饭中，假如你希望提前预加载子应用资源，可以在registerMicroApp时直接传入 importSource(…)，这个函数一执行，就会去请求资源回来并做缓存。但是，假如你不需要预加载，你想在子应用需要进入界面时（或打算让子应用进入界面时）才加载资源，则配置为 () =&amp;gt; importSource(…) ，这种配置会在子应用执行 bootstrap 的时候才去请求资源。&lt;/p&gt;
 &lt;h3&gt;环境隔离&lt;/h3&gt;
 &lt;p&gt;环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的，两个子应用之间，可能存在相互污染的问题，这就要求微前端框架实现一种能力，让子应用运行在自己的一个隔离环境中，从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;iframe：样式和脚本运行的隔离，缺点在于无法全屏弹出层&lt;/li&gt;
  &lt;li&gt;ShadowDOM：样式隔离，缺点在于弹出层被挂在document.body下面，而样式被放在ShadowDOM内部，无法正确渲染弹出层&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;h4&gt;快照沙箱&lt;/h4&gt;
 &lt;p&gt;多个子应用在页面上相互切换，而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png"&gt;   &lt;img alt="" height="206" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这种方案只适合同一时间只运行一个子应用的场景，例如腾讯云控制台。当子应用进入界面的时候，给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时，把window清理干净，再把快照上的属性重新添加到window上，复原了子应用挂载前的window。&lt;/p&gt;
 &lt;h4&gt;代理沙箱&lt;/h4&gt;
 &lt;p&gt;代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1. 创建代理对象&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;比如上面提到window可能被污染。那就创建一个window的代理对象，例如fakeWin，实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" rel="attachment wp-att-12270"&gt;   &lt;img alt="" height="212" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" width="799"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这样处理之后，我们在读取时可能读取到原始window上的值，但是一旦我们写入新属性之后，再读就读到刚才写入的值，但对于原始的window来说，没有被污染。&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;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" rel="attachment wp-att-12271"&gt;   &lt;img alt="" height="174" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上面代码里面的window, document, location等，都是前面创建好的代理对象。&lt;/p&gt;
 &lt;p&gt;当然，这里只给出了一些最核心思路的代码，实际上在真正实现时，还要考虑各种特殊情况，需要进行多方面的处理。&lt;/p&gt;
 &lt;p&gt;通过代理沙箱，子应用就可以在主应用中独立运行，而不会对主应用上的其他子应用产生负面影响。不过，值得一提的是，由于代理沙箱实际上虚拟了一个给子应用的环境来运行，也就意味着需要消耗更多的计算资源，会给子应用的性能带来一定影响。同时，由于这种虚拟环境在某些情况下必须连接到真实环境进行操作，或者从另外一面反过来说，虚拟环境中不一定能提供子应用所需要的全部依赖，这就会导致子应用中某些功能失效，甚至影响整个子应用的表现效果。&lt;/p&gt;
 &lt;h3&gt;路由映射&lt;/h3&gt;
 &lt;p&gt;如果子应用有自己的路由系统，处理不好，子应用在切换路由时会污染父应用，导致浏览器url发生变化，结果把当前页面切到另外一个地方去了。为了解决这种问题，麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的，所以，不同层的应用得到的location是不同的，基座应用使用浏览器的location，但是它的子应用则不是，修改浏览器的url之后，可以通过路由映射机制，伪造子应用得到的url。具体实现是通过创建一个临时的iframe，利用代理沙箱的能力，将子应用的location代理到iframe里面的location上去。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" rel="attachment wp-att-12274"&gt;   &lt;img alt="" height="334" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" width="696"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;得益于代理沙箱，子应用的url变化不会导致浏览器的url变化。&lt;/p&gt;
 &lt;p&gt;映射逻辑需要写一个map和reactive配置项，当浏览器的url发生变化时，通过map映射到子应用内部。子应用内部url发生变化时，通过reactive映射到浏览器，这样即使用户在某一时刻刷新浏览器，也可以通过url映射关系，准确还原子应用当前的界面。&lt;/p&gt;
 &lt;h3&gt;挂载&lt;/h3&gt;
 &lt;p&gt;在麦饭中，子应用需要通过一个 &amp;lt;mfy-app&amp;gt; 标签来决定子应用挂载在什么地方。和qiankun等框架不同，qiankun需要在子应用中决定挂载点，但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以，子应用在哪里挂载应该由父应用决定。&lt;/p&gt;
 &lt;p&gt;子应用被放在 &amp;lt;mfy-app&amp;gt; 中，给了开发者一些特殊的能力：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;可以放在 v-if 内部，DOM节点被移除后挂回来，子应用还在&lt;/li&gt;
  &lt;li&gt;动画效果&lt;/li&gt;
  &lt;li&gt;keepAlive&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在实现 &amp;lt;mfy-app&amp;gt; 时用到了一些比较 hack 的技巧。比如需要借助 &amp;lt;mfy-app&amp;gt; 这个节点所在作用域的顶层节点，在顶层节点DOM对象上挂载一些数据，通过这个技巧，确保节点被移除后，再被挂载回来时，还能正确还原之前界面。&lt;/p&gt;
 &lt;p&gt;keepAlive则是在 &amp;lt;mfy-app&amp;gt; 节点没有被移除的情况下，子应用执行 unmount 时，并没有实际销毁子应用构建的 DOM 树，而是放在内存中，当子应用再次 mount 的时候，直接把这个内存里面的 DOM 树挂载到 &amp;lt;mfy-app&amp;gt; 内部。&lt;/p&gt;
 &lt;h3&gt;通信/应用树&lt;/h3&gt;
 &lt;p&gt;这部分是麦饭设计中最复杂的部分，也是最终与其他微前端框架区别的地方。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" rel="attachment wp-att-12275"&gt;   &lt;img alt="" height="552" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" width="615"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;我构建了一个这样的树状数据结构，称之为“应用树”。它表达了基于 MFY 开发的微前端应用中，应用于子应用的引用关系。&lt;/p&gt;
 &lt;h4&gt;scope&lt;/h4&gt;
 &lt;p&gt;scope概念是指一个应用起来之后，会创建一个scope（作用域），这个 scope 保存了该应用的一些运行时信息，同时通过了通信的接口方法。一个应用可能会有多个子应用，这些子应用都有自己的 scope，上下级应用之间可以通过scope完成通信，比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令，接到这个指令后，两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令，而 parent_app 再把这个指令转发给了 child_app_1，这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。&lt;/p&gt;
 &lt;p&gt;rootScope 是一个特殊的scope，它对应的是基座应用，是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象，所以，利用 rootScope 可以完成跨层应用间的直接通信（虽然不推荐）。&lt;/p&gt;
 &lt;h4&gt;connectScope&lt;/h4&gt;
 &lt;p&gt;每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现，在同一层，实现逻辑有点像react hooks，你不需要关心你处于应用树的哪个位置，对于子应用开发团队而言，只需要在代码中使用connectScope()函数，就可以直接连接到自己所在的作用域。如果你实现过react hooks的话，应该能理解它的一个实现原理。但是由于一些实现上的限制，你不能异步执行connectScope，必须在代码第一次执行时，同步调用connectScope获取当前子应用的scope。&lt;/p&gt;
 &lt;h4&gt;状态共享&lt;/h4&gt;
 &lt;p&gt;“如果子应用1修改了用户的某个状态，子应用2怎么对这个修改做出响应？”&lt;/p&gt;
 &lt;p&gt;这个问题涉及到一个状态共享问题。由于我在设计时，坚持每个子应用团队应该封闭开发的理念，开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用，或者还需要依赖其他应用的状态变化，这会让我在开发的时候一直处于对当前应用状态的未知状态，那这样就没法调试和测试了。因此，设计中我直接拒绝实现子应用间的状态共享。&lt;/p&gt;
 &lt;p&gt;但是在实际使用过程中，这种需求是存在的。因此，我建议使用通信的方式解决，子应用1发出一个消息，通过 rootScope，通知网络我改变了用户状态，那么其他子应用在接受到这个消息之后，自己决定是否要重新渲染界面。&lt;/p&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;子应用自己还要加载资源（angularjs模板）绝对路径问题&lt;/li&gt;
  &lt;li&gt;登录态怎么传递？&lt;/li&gt;
  &lt;li&gt;多语言怎么配置？&lt;/li&gt;
  &lt;li&gt;代码共享（依赖）怎么处理？&lt;/li&gt;
  &lt;li&gt;运行时对象多个怎么办？（例如每个子应用都有自己的jQuery）&lt;/li&gt;
  &lt;li&gt;跨应用加载相同资源怎么办？（例如同时请求一个api拉取数据）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端不是万能的，坑也很多，所以应了那句话“没有银弹”。&lt;/p&gt;
 &lt;h2&gt;结语&lt;/h2&gt;
 &lt;p&gt;微前端是一种架构形式，一旦采用这种架构，就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式，因此，开发团队最好经过比较长一段时间的调研之后，才决定启用这种架构。从本文中，你也会发现，要实现微前端框架的核心能力，需要使用一些看上去不那么优雅的hack方法，既然是hack方法，就存在一定的弊端，比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点，在实际项目中，还需要面临更多问题，但这并不是说我在劝退大家，而是希望大家在选择时，根据实际的需求决定，不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣，可以在文章下面留言，我们一起探讨有关微前端框架的实现技术。&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/62164-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</guid>
      <pubDate>Tue, 22 Feb 2022 10:02:42 CST</pubDate>
    </item>
    <item>
      <title>程序员必知的分布式容错和降级技术</title>
      <link>https://itindex.net/detail/62161-%E7%A8%8B%E5%BA%8F%E5%91%98-%E5%88%86%E5%B8%83-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;em&gt;&lt;/em&gt; &lt;p&gt;可以参与抽奖&lt;/p&gt; &lt;em&gt;  &lt;br /&gt;&lt;/em&gt; &lt;p&gt;   &lt;br /&gt;&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;参与方式&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;关注公众号：35岁程序员那些事，后台回复关键词“参与抽奖”，获取抽奖链接，点击抽奖。 中奖之后，可以联系笔者的微信号或者公众号后台回复关键词“联系笔者”，获取联系方式。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;分布式容错和降级是微服务架构中应对瞬时大流量的最佳解决方案。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;推荐使用Spring Cloud Alibaba+Sentinel&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Nginx&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Nginx是一块轻量级的Web服务器/反向代理服务器，目前在github上Star 13.3k Fork 4.9k Watch 951，整体关注度也非常高，最近一次更新是2020年12月5日，最新的版本为release-1.19.6。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;那么如何使用Nginx进行应用的分布式容错和降级呢？答案就是Nginx+Lua可以实现分布式容错和降级，利用Lua脚本可以实现限流算法，并从应用接入层做容错和降级。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;  &lt;strong&gt;Guava&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Guava是一种基于开源的Java库，其中包含谷歌内部使用的很多核心库。这个库是为了方便编码，并减少编码错误。这个库提供了用于集合、缓存、支持原语、并发性、常见注解、字符串处理、I/O等实用方法API，这些都是谷歌开发者结合自身业务场景的最佳实践，可以说是一块非常优秀的开源中间件框架。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Guava - RateLimiter使用的是一种叫令牌桶的流控算法，RateLimiter会按照一定的频率往桶里扔令牌，线程拿到令牌才能执行。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Guava目前在github上Star 40k Fork 8.9k Watch 2.5k，最近一次更新是2020年12月15日，最新版本为30.1，无论是开源的关注度和活跃度都非常高。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Redis&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Redis是互联网技术领域使用最为广泛的存储中间件，它是“Remote Dictionary Service”（远程字典服务）的首字母缩写。Redis以其超高的性能、完美的文档、简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Redis采用C语言开发，目前在github上Star 47k Fork 18.5k Watch 2.7k，关注度也非常高，最近一次更新是2021年1月12日，目前最新版本为6.0.10，社区更新活跃度非常高。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在Redis中，有5种基础数据结构，分别为：string（字符串）、list（列表）、hash（字典）、set（集合）和zset（有序集合），合理的利用这些数据结构是可以达到分布式限流的效果，比如通过zset数据结构中的score值，来构造一个时间窗口，作为限流的滑动窗口，这样就可以快速的构造一个分布式限流算法。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Hystrix&lt;/strong&gt;  &lt;strong&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Hystrix是Netflix公司开源的一款容错框架，包含常用的容错方法：线程池隔离、信号量隔离、熔断和降级回退。在高并发访问下，系统所依赖的服务的稳定性对系统的影响非常大，依赖有很多不可控的因素，比如网络连接变慢、资源突然繁忙、暂时不可用、服务宕机等。我们要构建稳定、可靠的分布式系统，就必须要有一套容错的解决方案。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Hystrix目前在github上Star 20.9k fork 4.3k Watch 1.7k，可以说关注度非常高，但是最近一次版本更新是2018年11月17日，版本为v1.5.18，已经有两年没更新代码。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h3&gt;  &lt;strong&gt;Resilience4j&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;Resilience4j 是一个比较轻量的熔断降级库。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;首先，Resilience4j 的模块化做的比较好，将每个功能点（如熔断、限速器、自动重试）都拆成了单独的模块，这样整体结构很清晰，用户也只需要引入相应功能的依赖即可。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;另外，Resilience4jR是针对 Java 8 和函数式编程设计的，API 比较简洁优雅。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;同时，与 Hystrix 相比，Resilience4j 增加了简单的限速器和自动重试特性，使用场景更加丰富。Resilience4j 属于一个新兴项目，社区也在蓬勃发展。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;总的来说，Resilience4j 是比较轻量的库，在较小较新的项目中使用还是比较方便的。但是 Resilience4j 只包含限流降级的基本场景，对于非常复杂的企业级服务架构可能无法很好地 cover 住；同时 Resilience4j 缺乏生产级别的配套设施（如提供规则管理和实时监控能力的控制台）。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Resilience4j目前在github上Star 6.4k Fork 857 Watch 215，最近一次版本更新是2020年10月19号，最新的release版本是v1.6.1。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Sentinel&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Sentinel 一款面向分布式服务架构的轻量级流量控制组件，主要以流量为切入点，从流量控制、熔断降级、系统自适应保护等多个维度来帮助用户保障服务的稳定性。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Sentinel 的核心思想：根据对应资源配置的规则来为资源执行相应的流控/降级/系统保护策略。在 Sentinel 中资源定义和规则配置是分离的。用户先通过 Sentinel API 给对应的业务逻辑定义资源，然后可以在需要的时候动态配置规则。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Sentinel 的优势和特性：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;轻量级，核心库无多余依赖，性能损耗小；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;方便接入，开源生态广泛。Sentinel对Dubbo、Spring Cloud、Web Servlet、gRPC等常用框架提供适配模块，只需引入相应依赖并简单配置即可快速接入；同时针对自定义的场景Sentinel还提供低侵入性的注解资源定义方式，方便自定义接入；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;丰富的流量控制场景。Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景，流控维度包括流控指标、流控效果（塑形）、调用关系、热点、集群等各种维度，针对系统维度也提供自适应的保护机制；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;易用的控制台，提供实时监控、机器发现、规则管理等能力；&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt; 完善的扩展性设计，提供多样化的 SPI 接口，方便用户根据需求给 Sentinel 添加自定义的逻辑。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;  &lt;strong&gt;对比以上几种解决方案&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Nginx和Redis具备一定的分布式容错和降级能力，但是从功能完整性角度肯定是不如Sentinel、Hystrix和Resilience4j，Guava虽然也具备容错和降级能力，但是不具备分布式能力，它是基于单实例JVM的。基于以上原因，这里就对比下Sentinel、Hystrix和Resilience4j。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="123"&gt;    &lt;br /&gt;&lt;/td&gt;   &lt;td valign="top" width="123"&gt;Sentinel&lt;/td&gt;   &lt;td valign="top" width="123"&gt;Hystrix&lt;/td&gt;   &lt;td valign="top" width="123"&gt;Resilience4j&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td valign="top" width="123"&gt;隔离策略&lt;/td&gt;   &lt;td valign="top" width="123"&gt;信号量隔离（并发线程数限流）&lt;/td&gt;   &lt;td valign="top" width="123"&gt;线程池隔离/信号量隔离&lt;/td&gt;   &lt;td valign="top" width="123"&gt;信号量隔离&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td valign="top" width="123"&gt;熔断降级策略&lt;/td&gt;   &lt;td valign="top" width="123"&gt;基于响应时间、异常比率、异常数&lt;/td&gt;   &lt;td valign="top" width="123"&gt;基于异常比率&lt;/td&gt;   &lt;td valign="top" width="123"&gt;基于异常比率、响应时间&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td valign="top" width="123"&gt;实时指标实现&lt;/td&gt;   &lt;td valign="top" width="123"&gt;滑动窗口（LeapArray）&lt;/td&gt;   &lt;td valign="top" width="123"&gt;滑动窗口（基于RxJava）&lt;/td&gt;   &lt;td valign="top" width="123"&gt;Ring Bit Buffer&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;动态规则配置&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持多种数据源&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持多种数据源&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;有限支持&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;扩展性&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;多个扩展点&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;插件形式&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;SDK形式&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;基于注解的支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;限流&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;基于QPS限流和基于调用关系限流&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;有限的支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;Rate Limiter&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;流量整形&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持预热模式、匀速器模式和预热排队模式&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;不支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;简单的Rate Limiter模式&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;系统自适应保护&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;不支持&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;不支持&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;控制台&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;提供开箱即用的控制台，可配置规则、查看秒级监控、机器治理等。&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;简单的监控查看&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;不提供控制台，可对接其它监控系统。&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;常见框架适配&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;Servlet、HttpClient、Dubbo、Spring Web等&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;Servlet、Spring Cloud Netflix&lt;/td&gt;   &lt;td colspan="1" rowspan="1" valign="top"&gt;部分支持&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;推荐使用Spring Cloud Alibaba+Sentinel，可以从服务治理和流量治理，两重维度去治理微服务，高效、简单并且符合主流。&lt;/p&gt; &lt;p&gt;  &lt;br /&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>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62161-%E7%A8%8B%E5%BA%8F%E5%91%98-%E5%88%86%E5%B8%83-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Sun, 13 Mar 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>数仓建模—ID Mapping - 大数据技术派 - 博客园</title>
      <link>https://itindex.net/detail/62145-%E5%BB%BA%E6%A8%A1-id-mapping</link>
      <description>&lt;div&gt;    &lt;p&gt;早晨起床的时候，发现自己尿分叉，我没有多想，简单洗洗就匆忙出门。路过早餐店，我看到师傅熟练的拉扯一小块面团，拉至细长条，然后放入油锅中，不一会功夫，一根屎黄色的油条便出锅了，卖相不错。我在想，小到炸屎黄色的油条，大到学习，其实都是一个熟能生巧的过程。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;数据仓库系列文章(持续更新)&lt;/strong&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1582" rel="noopener" target="_blank"&gt;数仓架构发展史&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1551" rel="noopener" target="_blank"&gt;数仓建模方法论&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1559" rel="noopener" target="_blank"&gt;数仓建模分层理论&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1954" rel="noopener" target="_blank"&gt;数仓建模—宽表的设计&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1957" rel="noopener" target="_blank"&gt;数仓建模—指标体系&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=1587" rel="noopener" target="_blank"&gt;数据仓库之拉链表&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;数仓—数据集成&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=2007" rel="noopener" target="_blank"&gt;数仓—数据集市&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;数仓—商业智能系统&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://www.ikeguang.com/?p=2008" rel="noopener" target="_blank"&gt;数仓—埋点设计与管理&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;数仓—ID Mapping&lt;/li&gt;      &lt;li&gt;数仓—OneID&lt;/li&gt;      &lt;li&gt;数仓—AARRR海盗模型&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;blockquote&gt;      &lt;p&gt;关注        &lt;code&gt;大数据技术派&lt;/code&gt;，回复:        &lt;code&gt;资料&lt;/code&gt;，领取        &lt;code&gt;1024G&lt;/code&gt;资料。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;顾名思义我们知道ID Mapping 的操作对象是ID，目标或者是动作是Mapping，也就是说我们要做的事情其实就是想把不同平台不同设备上的ID 打通，从而更好的去刻画用户，也就是说我们希望能打通用户各个维度的数据，从而更好的去服务业务服务用户&lt;/p&gt;    &lt;p&gt;通常公司有产品矩阵，而每个产品都有自己的注册账号产生的用户ID。从公司全局，整合用户表，用户行为数据来看，确定不同产品的用户ID是相同一个人非常重要, 选取合适的用户标识对于提高用户行为分析的准确性有非常大的影响，尤其是对用户画像、推荐、漏斗、留存、Session 等用户相关的分析功能。&lt;/p&gt;    &lt;p&gt;其实对于任何分析都是一样的，如果我们不能准确标识一个用户，那么我们的计算结果就没有准确性可言，其实对于数据服务方而言，数据的准确性是我们的第一要务，      &lt;strong&gt;我们宁愿不出数据，也不要出错误的数据&lt;/strong&gt;。&lt;/p&gt;    &lt;h3&gt;ID Mapping 的背景&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;网络身份证&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;假如没有网络身份证，那么每个商家（App）只能基于自己的账号体系标识用户，并记录用户的行为。而有了统一的网络身份证之后，各个商家之间的数据就可以打通了，天猫不仅知道用户A在淘宝系的购物数据，也能了解到该用户在社交网络的行为，以及旅游的喜好，等等。&lt;/p&gt;    &lt;p&gt;在现实的数据中，由于，用户可能使用各种各样的设备，有着各种各样的前端入口，甚至同一个用户拥有多个设备以及使用多种前端入口，就会导致，日志数据中对同一个人，不同时间段所收集到的日志数据中，可能取到的标识个数、种类各不相同；&lt;/p&gt;    &lt;p&gt;比如用户可能使用各种各样的设备，其次是不同设备有不同的操作系统，设置是软件本身的版本也会影响我们对用户的标识，&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;手机、平板电脑、PC&lt;/li&gt;      &lt;li&gt;安卓手机、ios手机、winphone手机&lt;/li&gt;      &lt;li&gt;安卓系统有各种版本 （ 5.0 6.0 7.0 8.0 9.0 ）&lt;/li&gt;      &lt;li&gt;ios系统也有各种版本（3.x 4.x 5.x 6.x 7.x … 12.x ）&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;      &lt;strong&gt;存在的问题&lt;/strong&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;用户设备的标识，没办法轻易定制一个规则来取某个作为唯一标识：&lt;/li&gt;      &lt;li&gt;mac地址：手机网卡物理地址， 若干早期版本的ios，winphone，android可取到&lt;/li&gt;      &lt;li&gt;imei(入网许可证序号)：安卓系统可取到，若干早期版本的ios，winphone可取到，运营商可取到&lt;/li&gt;      &lt;li&gt;imsi(手机SIM卡序号)：安卓系统可取到，若干早期版本的ios，winphone可取到，运营商可取到&lt;/li&gt;      &lt;li&gt;androidid ：安卓系统id&lt;/li&gt;      &lt;li&gt;openuuid(app自己生成的序号) ：卸载重装app就会变更&lt;/li&gt;      &lt;li&gt;IDFA(广告跟踪码)&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;      &lt;strong&gt;扩展 IDFA&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;IDFA，英文全称 Identifier for Advertising ，可以理解为广告id，苹果公司提供的用于追踪用户的广告标识符，可以用来打通不同app之间的广告。      &lt;strong&gt;每个设备只有一个IDFA&lt;/strong&gt;，不同APP在同一设备上获取IDFA的结果是一样的&lt;/p&gt;    &lt;p&gt;苹果为了保护用户隐私，早在2012年就不再允许其生态中的玩家获取用户的唯一标识符，但是商家在移动端打广告的时候又希望能监控到每一次广告投放的效果，因此，苹果想出了折中的办法，就是提供另外一套和硬件无关的标识符，用于给商家监测广告效果，同时用户可以在设置里改变这串字符，导致商家没有办法长期跟踪用户行为。这个就叫做广告标识符（IDFA），设置路径是“设置-&amp;gt;隐私-&amp;gt;广告-&amp;gt;还原广告标识符”，如下图所示（iOS9）&lt;/p&gt;    &lt;p&gt;      &lt;img alt="img" src="https://kingcall.oss-cn-hangzhou.aliyuncs.com/blog/img/v2-045a372ba062e8215e46f360f681582c_1440w.jpg"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;常见的标识&lt;/h3&gt;    &lt;h4&gt;设备 ID&lt;/h4&gt;    &lt;p&gt;需要注意的是，设备 ID 并不一定是设备的唯一标识。例如 Web 端的 Cookies 有可能被清空（例如各种安全卫士），而 iOS 端的 IDFV( Identifier For Vendor)在不同厂商的 App 间是不同的,而且重新安装IDFV会被重置。&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;设备&lt;/th&gt;        &lt;th&gt;规则&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;Android&lt;/td&gt;        &lt;td&gt;1.10.5 之前版本，默认使用 UUID（例如：550e8400-e29b-41d4-a716-446655440000），App 卸载重装 UUID 会变，为了保证设备 ID 不变，可以通过配置使用 AndroidId（例如：774d56d682e549c3）；1.10.5 及之后的版本 SDK 默认使用 AndroidId 作为设备 ID，如果 AndroidId 获取不到则获取随机的 UUID。&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;iOS&lt;/td&gt;        &lt;td&gt;1.10.18 及之后版本，如果 App 引入了 AdSupport 库，SDK 默认使用 IDFA 作为匿名 ID。1.10.18 之前版本，可以优先使用 IDFV（例如：1E2DFA10-236A-47UD-6641-AB1FC4E6483F），如果 IDFV 获取失败，则使用随机的 UUID（例如：550e8400-e29b-41d4-a716-446655440000），一般情况下都能够获取到 IDFV。如果使用 IDFV 或 UUID ，当用户卸载重装 App 时设备 ID 会变。也可以通过配置使用 IDFA（例如：1E2DFA89-496A-47FD-9941-DF1FC4E6484A），如果开启 IDFA ，可以优先获取 IDFA，如果获取失败再尝试获取 IDFV。使用 IDFA 能避免用户在重装 App 后设备 ID 发生变化的情况，需要注意的是IDFA 也是可以被重置的&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;h4&gt;登录 ID&lt;/h4&gt;    &lt;p&gt;登录 ID 通常是业务数据库里的主键或其它唯一标识。所以 登录 ID，相对来说更精确更持久。但是，用户在使用时不一定注册或者登录，而这个时候是没有      &lt;em&gt;        &lt;strong&gt;登录 ID&lt;/strong&gt;&lt;/em&gt;的。&lt;/p&gt;    &lt;h4&gt;平台 ID&lt;/h4&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;设备&lt;/th&gt;        &lt;th&gt;规则&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;JavaScript&lt;/td&gt;        &lt;td&gt;默认情况下使用 cookie_id（例如：15ffdb0a3f898-02045d1cb7be78-31126a5d-250125-15ffdb0a3fa40a），cookie_id 存贮在浏览器的 cookie 中，依然有被重置的风险          &lt;br /&gt;这里还有一个问题，就是 cookie 只能隶属于同一个域名，也就是说你访问邮箱的 cookie ，与百度广的 cookie 并不是同一个，所以在网站与网站也要做 ID Mapping ，这就是为什么你在百度上搜索了“养生”，到购物网站上就会给你推荐“枸杞”。&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;微信小程序&lt;/td&gt;        &lt;td&gt;默认情况下使用 UUID（例如：1558509239724-9278730-00c1875d5f63f8-41373096），但是删除小程序，UUID 会变。为了保证设备 ID 不变，建议获取并使用 openid（例如：oWDMZ0WHqfsjIz7A9B2XNQOWmN3E）。 如果选择使用 openid 的话，请注意【操作暂存】，由于获取 openid 是一个异步的操作，但是冷启动事件等会先发生，这时候这个冷启动事件的 distinct_id 就不对了。所以我们会把先发生的操作，暂存起来，等获取到 openid 等后调用 sa.init() 后才会发送数据。 openid 的获取和操作暂存的方法。&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;其实这里的平台指的一些大的生态系统，例如微信的生态、今日头条等，我们很多依赖这些平台的应用就可以使用用户在这些平台上的用户标识作为标识,当然我们也可以在此基础上为我们自己平台上的用户创建属于本平台的用户表示，往往就是用户的      &lt;strong&gt;登录ID&lt;/strong&gt;&lt;/p&gt;    &lt;h3&gt;方案详解&lt;/h3&gt;    &lt;p&gt;因此，我们在进行任何数据接入之前，都应当先确定如何来标识用户。下面会介绍几种用户标识方案的原理，以及几种典型情况下的用户标识方案。&lt;/p&gt;    &lt;h4&gt;方案一：只使用设备 ID&lt;/h4&gt;    &lt;p&gt;适合没有用户注册体系，或者极少数用户会进行多设备登录的产品，如工具类产品、搜索引擎、部分电商等。这也是绝大多数其它数据分析产品唯一提供的方案。&lt;/p&gt;    &lt;h5&gt;局限性&lt;/h5&gt;    &lt;ul&gt;      &lt;li&gt;同一用户在不同设备使用会被认为不同的用户，对后续的分析统计有影响。&lt;/li&gt;      &lt;li&gt;不同用户在相同设备使用会被认为是一个用户，也对后续的分析统计有影响。&lt;/li&gt;      &lt;li&gt;但如果        &lt;strong&gt;用户跨设备使用或者多用户共用设备不是产品的常见场景的话&lt;/strong&gt;，可以忽略上述问题。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;方案二：关联设备 ID 和登录 ID（一对一）&lt;/h4&gt;    &lt;p&gt;仅使用 设备 ID 标识用户虽然简单，但是对于某些应用场景这种方式不够准确，因此我们可以采用 设备 ID 和 登录 ID 的方案，在一定程度上融合 设备 ID 和 登录 ID，从而实现更准确的用户追踪。&lt;/p&gt;    &lt;p&gt;成功关联设备 ID 和登录 ID 之后，      &lt;strong&gt;用户在该设备 ID 上或该登录 ID 下的行为就会贯通&lt;/strong&gt;，被认为是一个用户 ID 发生的。在进行事件、漏斗、留存等用户相关分析时也会算作一个用户。&lt;/p&gt;    &lt;p&gt;关联设备 ID 和登录 ID 的方法虽然实现了更准确的用户追踪，但是也会增加埋点接入的复杂度。所以一般来说，我们建议只有当同时满足以下条件时，才考虑进行 ID 关联：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;需要贯通一个用户在一个设备上注册前后的行为。&lt;/li&gt;      &lt;li&gt;需要贯通一个注册用户在不同设备上登录之后的行为。&lt;/li&gt;&lt;/ol&gt;    &lt;h5&gt;局限性&lt;/h5&gt;    &lt;ul&gt;      &lt;li&gt;一个设备 ID 只能和一个登录 ID 关联，而事实上一台设备可能有多个用户使用。&lt;/li&gt;      &lt;li&gt;一个登录 ID 只能和一个设备 ID 关联，而事实上一个用户可能用一个登录 ID 在多台设备上登录。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;方案三：关联设备 ID 和登录 ID（多对一）&lt;/h4&gt;    &lt;p&gt;      &lt;strong&gt;关联设备 ID 和登录 ID（一对一）虽然已经实现了跨设备的用户贯通，但是对于某些应用场景还是不够准确，因此这里提供一个新的关联方案，支持一个登录 ID 绑定多个设备 ID 的方案，从而实现更准确的用户追踪&lt;/strong&gt;。&lt;/p&gt;    &lt;p&gt;也就是跨端，其实这是非常常见的一种场景，例如你在PC 端和 移动端使用同一个产品。&lt;/p&gt;    &lt;p&gt;一个用户在多个设备上进行登录是一种比较常见的场景，比如 Web 端和 App 端可能都需要进行登录。支持一个登录 ID 下关联多设备 ID 之后，用户在多设备下的行为就会贯通，被认为是一个ID 发生的。&lt;/p&gt;    &lt;h5&gt;局限性&lt;/h5&gt;    &lt;ul&gt;      &lt;li&gt;一个设备 ID 只能和一个登录 ID 关联，而事实上        &lt;strong&gt;一台设备可能有多个用户使用&lt;/strong&gt;多用户使用同一个设备这个问题是无解的。&lt;/li&gt;      &lt;li&gt;一个设备 ID 一旦跟某个登录 ID 关联或者一个登录 ID 和一个设备 ID 关联，就不能解除（自动解除）。而事实上，设备 ID 和登录 ID 的动态关联才应该是更合理的。&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;方案对比&lt;/h3&gt;    &lt;p&gt;将上述三个方案放到一起，可以明显看到三种方案的区别，如下表所示：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;方案一：仅使用设备 ID，不管用户是谁，只要设备未变，设备ID 就不变，即使多人使用同一个设备，也会被识别为同一个用户。&lt;/li&gt;      &lt;li&gt;方案二：关联设备 ID 和登录 ID（一对一），        &lt;ul&gt;          &lt;li&gt;当用户换手机后，登录账号之后的行为与换手机之前的行为贯通了，但是在新设备上首次登录之前的行为仍没法贯通，仍被识别为新的用户的行为。&lt;/li&gt;          &lt;li&gt;当用户把旧手机送给朋友之后，由于旧手机已被关联到自己的登录 ID 了，无法再与朋友的登录 ID 关联。后续使用这台旧手机的用户们，若不登录就操作，则都会被识别为同一个用户。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;      &lt;li&gt;方案三：关联设备 ID 和登录 ID（多对一）        &lt;ul&gt;          &lt;li&gt;当用户把旧手机送给朋友之后，由于旧手机已被关联到自己的登录 ID 了，无法再与朋友的登录 ID 关联。后续使用这台旧手机的用户们，若不登录就操作，则都会被识别为同一个用户）。&lt;/li&gt;          &lt;li&gt;而事实上，旧手机上后续的匿名登录很难识别到底是谁，可能归为匿名登录之前最近一次登录的用户会更合理一些。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;其实，三种方案没有对与错，我们应该结合自己的业务场景以及埋点复杂度来选择合适的方案。&lt;/p&gt;    &lt;h2&gt;总结&lt;/h2&gt;    &lt;p&gt;ID Mapping 就如同它的名字一样，我们要做的就是将一系列的ID 关联起来，一些列的ID 可能是用户在不同平台上的标识，也可能是用户在不同设备上的标识，也可能是用户在不同状态下的标识，总之我们就是要将这一系列的ID 关联起来，尽可能地将用户的数据打通，从而提供更加全面准确的分析。&lt;/p&gt;    &lt;p&gt;我们知道只有打破数据孤岛数据才能发挥更大的价值，可能很多人都知道数据仓库的数据集成环节其实就是为了打破数据孤岛，其实我们的ID Mapping 也是为了打破数据孤岛，其实ID Mapping 就两个使命 1. 多端数据的识别；2. 多源数据的打通，其他的都是基于ID Mapping 的应用&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62145-%E5%BB%BA%E6%A8%A1-id-mapping</guid>
      <pubDate>Sat, 05 Mar 2022 19:54:24 CST</pubDate>
    </item>
    <item>
      <title>六张表格让你快速搞定新技术团队</title>
      <link>https://itindex.net/detail/62131-%E8%A1%A8%E6%A0%BC-%E6%8A%80%E6%9C%AF-%E5%9B%A2%E9%98%9F</link>
      <description>&lt;h1&gt;前言&lt;/h1&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;li&gt;确定好目标并执行&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;前三步中我总结了六个表格来梳理这些结构，在这些结构的基础上我们明确团队的目标，并执行下去，将会有一个不错的结果。&lt;/p&gt;
 &lt;h1&gt;1. 弄清楚有哪些人&lt;/h1&gt;
 &lt;p&gt;弄清楚有哪些人的关键点是人才梯队和人才发展计划的表格，确认哪些会有下一步晋升，哪些可能会走，哪些是有问题的，做好 1v1 沟通，先熟悉起来，了解每个人的工作状态，特别是核心骨干。&lt;/p&gt;
 &lt;h2&gt;1.1 成员信息表&lt;/h2&gt;
 &lt;p&gt;成员信息表能帮助你了解你的团队成员的背景，在职时间等，这些信息可以找 HR 或者之前团队的负责人去了解，或者当面沟通的时候了解等。&lt;/p&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;序号&lt;/th&gt;
    &lt;th&gt;部门&lt;/th&gt;
    &lt;th&gt;职位&lt;/th&gt;
    &lt;th&gt;职场&lt;/th&gt;
    &lt;th&gt;职级&lt;/th&gt;
    &lt;th&gt;姓名&lt;/th&gt;
    &lt;th&gt;性别&lt;/th&gt;
    &lt;th&gt;工号&lt;/th&gt;
    &lt;th&gt;家乡&lt;/th&gt;
    &lt;th&gt;工作年限&lt;/th&gt;
    &lt;th&gt;年龄&lt;/th&gt;
    &lt;th&gt;在职状态&lt;/th&gt;
    &lt;th&gt;在职时长&lt;/th&gt;
    &lt;th&gt;出生年份&lt;/th&gt;
    &lt;th&gt;毕业时间&lt;/th&gt;
    &lt;th&gt;入职时间&lt;/th&gt;
    &lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;1&lt;/td&gt;
    &lt;td&gt;研发中心&lt;/td&gt;
    &lt;td&gt;前端&lt;/td&gt;
    &lt;td&gt;深圳&lt;/td&gt;
    &lt;td&gt;T10&lt;/td&gt;
    &lt;td&gt;张三&lt;/td&gt;
    &lt;td&gt;男&lt;/td&gt;
    &lt;td&gt;00134&lt;/td&gt;
    &lt;td&gt;湖南&lt;/td&gt;
    &lt;td&gt;8&lt;/td&gt;
    &lt;td&gt;30&lt;/td&gt;
    &lt;td&gt;已转正&lt;/td&gt;
    &lt;td&gt;3&lt;/td&gt;
    &lt;td&gt;1993&lt;/td&gt;
    &lt;td&gt;2013&lt;/td&gt;
    &lt;td&gt;2019-10-01&lt;/td&gt;
    &lt;td&gt;核心，部门老员工&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&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;li&gt;看工作年限，看年轻人多，还是资深的人多；&lt;/li&gt;
  &lt;li&gt;看职位，各技术工种占比，配比是否合适；&lt;/li&gt;
  &lt;li&gt;看老家分布，下次出去吃饭点菜心里会有点谱。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;1.2 人才梯队表&lt;/h2&gt;
 &lt;p&gt;人才梯队表主要是帮助你理清当前团队的梯队情况，哪些是能带项目的，哪些是只能执行的，以及当有人才流失的时候，哪些同学是可以补位的。&lt;/p&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;-&lt;/th&gt;
    &lt;th&gt;前端&lt;/th&gt;
    &lt;th&gt;后端&lt;/th&gt;
    &lt;th&gt;移动端&lt;/th&gt;
    &lt;th&gt;QA&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;负责人&lt;/td&gt;
    &lt;td&gt;张三&lt;/td&gt;
    &lt;td&gt;李四&lt;/td&gt;
    &lt;td&gt;王五&lt;/td&gt;
    &lt;td&gt;赵六&lt;/td&gt;
&lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;第一梯队&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;第二梯队&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
 &lt;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;h2&gt;1.3 人才发展计划表&lt;/h2&gt;
 &lt;p&gt;人才发展计划表主要是识别出团队中成员的当前状态，以及下一步的发展计划。&lt;/p&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;姓名&lt;/th&gt;
    &lt;th&gt;当前状态&lt;/th&gt;
    &lt;th&gt;下一步计划&lt;/th&gt;
    &lt;th&gt;风险&lt;/th&gt;
    &lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;张三&lt;/td&gt;
    &lt;td&gt;骨干&lt;/td&gt;
    &lt;td&gt;职级晋升&lt;/td&gt;
    &lt;td&gt;后备 leader&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;李四&lt;/td&gt;
    &lt;td&gt;执行&lt;/td&gt;
    &lt;td&gt;后备培养&lt;/td&gt;
    &lt;td&gt;轻微离职倾向&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;王五&lt;/td&gt;
    &lt;td&gt;待考察&lt;/td&gt;
    &lt;td&gt;待观察&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;赵六&lt;/td&gt;
    &lt;td&gt;绩效不佳&lt;/td&gt;
    &lt;td&gt;淘汰&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
 &lt;p&gt;人才发展计划表主要是了解团队的成员的具体情况，多和老员工去聊聊，工作接触中多观察，并且可以通过下一级的 Leader 了解一线同学的状态，这个过程中可能会有一些信息的过滤，需要批判的接收信息。&lt;/p&gt;
 &lt;h2&gt;1.4 做好沟通&lt;/h2&gt;
 &lt;p&gt;做事之前先做人，做人之前先熟悉。&lt;/p&gt;
 &lt;p&gt;对于新同学，先认识一下，让对方知道你是怎样的人，以及了解一下对方是怎样的人，背景如何。可以从 HR 或其它渠道拿到简历等信息。&lt;/p&gt;
 &lt;p&gt;在破冰后可以多聊一下现在手头的工作内容以及将来团队的情况，目标等。 沟通过程可以适当的记录一些信息，整理到上面的成员信息表和人才发展计划表中。 至少都做一次 1v1 沟通，以及一次全员沟通，全员沟通可以和周例会合到一起。&lt;/p&gt;
 &lt;h1&gt;2. 弄清楚有哪些事&lt;/h1&gt;
 &lt;p&gt;在理清人的同时，也要同时开始了解有哪些事情。&lt;/p&gt;
 &lt;p&gt;作为技术管理者，多看文档，多看代码，多看表结构。多和原来负责的产品沟通，聊聊天，和原来负责的开发也多勾兑勾兑，在这个过程中把一些信息落成表格。&lt;/p&gt;
 &lt;h2&gt;2.1 业务模块重点问题表&lt;/h2&gt;
 &lt;p&gt;重点问题表主要是提取当前业务的重点问题，并从中找出能早点出成果的破局点。&lt;/p&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;模块&lt;/th&gt;
    &lt;th&gt;重点问题&lt;/th&gt;
    &lt;th&gt;当前状态&lt;/th&gt;
    &lt;th&gt;当前负责人&lt;/th&gt;
    &lt;th&gt;解决方案&lt;/th&gt;
    &lt;th&gt;后续计划&lt;/th&gt;
    &lt;th&gt;相关文档&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;核心链路&lt;/td&gt;
    &lt;td&gt;稳定性问题&lt;/td&gt;
    &lt;td&gt;梳理中&lt;/td&gt;
    &lt;td&gt;张三&lt;/td&gt;
    &lt;td&gt;可用性治理&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
 &lt;p&gt;从以上的表格中找到能快速解决大家工作中的痛点或者业务痛点的问题，尽量以 ROI 较高的方式处理，打破局面，获得一定的信任，当这种小的成功累积多了，你在团队中的口碑也就建立起来了，后续要做一些大的改变也顺理成章。&lt;/p&gt;
 &lt;h2&gt;2.2 RASCI 矩阵表&lt;/h2&gt;
 &lt;p&gt;RASCI 矩阵表主要是澄清项目组成员权利与责任，明确事项，作为项目管理中必要的沟通工具存在。&lt;/p&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;项目名称&lt;/th&gt;
    &lt;th&gt;执行（R）&lt;/th&gt;
    &lt;th&gt;决策(A)&lt;/th&gt;
    &lt;th&gt;顾问(C)&lt;/th&gt;
    &lt;th&gt;通知(I)&lt;/th&gt;
    &lt;th&gt;支持(S)&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;订单系统&lt;/td&gt;
    &lt;td&gt;研发二组&lt;/td&gt;
    &lt;td&gt;产品组王五&lt;/td&gt;
    &lt;td&gt;李四&lt;/td&gt;
    &lt;td&gt;商业化部门&lt;/td&gt;
    &lt;td&gt;中台/ SRE&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
 &lt;p&gt;有了 RASCI 矩阵的表，我们就可以知道业务边界在哪里，出了问题应该找谁，找谁去决策问题，做好了找谁去汇报或通知。&lt;/p&gt;
 &lt;h1&gt;3. 把人和事关联起来&lt;/h1&gt;
 &lt;p&gt;人和事关联，主要是找到事情的是什么，是团队的什么人在负责什么内容，这里重点要关注模块负责人。&lt;/p&gt;
 &lt;h2&gt;3.1 模块负责人及成员表&lt;/h2&gt;
 &lt;div&gt;
  &lt;table&gt;

   &lt;tr&gt;
    &lt;th&gt;业务线&lt;/th&gt;
    &lt;th&gt;前端&lt;/th&gt;
    &lt;th&gt;后端&lt;/th&gt;
    &lt;th&gt;移动端&lt;/th&gt;
    &lt;th&gt;QA&lt;/th&gt;
    &lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;


   &lt;tr&gt;
    &lt;td&gt;订单系统&lt;/td&gt;
    &lt;td&gt;张三&lt;/td&gt;
    &lt;td&gt;李四&lt;/td&gt;
    &lt;td&gt;王五（模块负责人）、赵六&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
    &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
 &lt;p&gt;模块负责人及成员表主要是理清具体某个事项是谁在负责，谁在执行。而模块负责人是一块业务的技术负责人，是一个小的技术 Leader ，负责掌控该业务模块，包括人和技术，是各端 Owner 的后备人才池，这个制度是人才梯队的核心制度之一。&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;li&gt;对模块负责，推进并协调模块成员解决问题；&lt;/li&gt;
  &lt;li&gt;迭代完成后回顾模块内的需求；&lt;/li&gt;
  &lt;li&gt;关注并引导模块成员成长。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h1&gt;4. 明确团队的目标&lt;/h1&gt;
 &lt;p&gt;这里更多是的向上沟通，和产品，老板多做沟通，了解公司整体目标，业务目标，技术目标等，在这些目标的基础上结合当前团队的现状，和团队成员做一定范围内的讨论，一起制定出团队的目标。&lt;/p&gt;
 &lt;p&gt;在初步确定目标后，和上级沟通团队目标，并达成一致。&lt;/p&gt;
 &lt;h1&gt;5. 清晰目标和持续跟进&lt;/h1&gt;
 &lt;p&gt;达成共识的目标后，将目标分解向下同步，确保大家对于目标能有清晰的认识。&lt;/p&gt;
 &lt;p&gt;这里我们会用到 OKR 、KPI 等技术，通过周报将 OKR/KPI 和具体的产品需求，技术需求关联起来，将目标，过程需求，过程进展，业务价值通过周报的逻辑持续的跟进起来，反思一周之中我们所做的内容是否向着我们的目标前进。&lt;/p&gt;
 &lt;p&gt;同时，建立沟通机制，周报机制，周会逻辑等等。&lt;/p&gt;
 &lt;h1&gt;结语&lt;/h1&gt;
 &lt;p&gt;接手新团队不是一个一蹴而就的过程，需要时间，把工作做实，该聊的天要聊到位，该开会的会要开，该喝的酒不能少。 就像一个又大又重的飞轮，开始的时候很艰难，努力转动一厘米，两厘米，一段时间后，发现转完了一整圈。飞轮开始转动，不停的努力，飞轮又会转完第二圈。继续沿向一个方向努力，3 圈 …… 4 圈 …… 5 圈 …… 6 圈，轮子开始加速，7 圈 …… 8 圈 …… 9 圈 …… 10 圈 …… 轮子有了动量，已经可以开始自己转动了，再坚持就能很快的飞转了。&lt;/p&gt;
 &lt;p&gt;最后引用冯唐的九字真言「不着急，不害怕，不要脸」，2022，加油吧！&lt;/p&gt;
 &lt;p&gt;模板在这：&lt;/p&gt;
 &lt;p&gt;飞书 Docs Link: https://fgr6cngqqy.feishu.cn/space/sheet/shtcnywzAOBxaeiU5ofJTYNXrGb Password: x1w8&lt;/p&gt;
 &lt;p&gt;
&lt;/p&gt; &lt;blockquote&gt;
  &lt;blockquote&gt;   &lt;p&gt;你好，我是潘锦，超过 10 年的研发管理和技术架构经历，出过书，创过业，带过百人团队，也在腾讯，A 股上市公司呆过一些年头，现在在一家 C 轮的公司负责一些技术方面的管理工作。早年做过 NOI 和 ACM，对前端架构、跨端、后端架构、云原生、DevOps 等技术始终保持着浓厚的兴趣，平时喜欢读书、思考，终身学习实践者，欢迎一起交流学习。微信公众号：架构和远方，博客： www.phppan.com&lt;/p&gt;&lt;/blockquote&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>架构和远方 团队管理 空降落地 管理新手</category>
      <guid isPermaLink="true">https://itindex.net/detail/62131-%E8%A1%A8%E6%A0%BC-%E6%8A%80%E6%9C%AF-%E5%9B%A2%E9%98%9F</guid>
      <pubDate>Sat, 26 Feb 2022 20:03:58 CST</pubDate>
    </item>
    <item>
      <title>微前端框架核心技术揭秘</title>
      <link>https://itindex.net/detail/62121-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</link>
      <description>&lt;img alt="&amp;#24494;&amp;#21069;&amp;#31471;&amp;#26694;&amp;#26550;&amp;#26680;&amp;#24515;&amp;#25216;&amp;#26415;&amp;#25581;&amp;#31192;" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/mf-2.png"&gt;&lt;/img&gt;
 &lt;br /&gt;

 &lt;p&gt;2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”（Micro Frontend），其后该概念在web领域逐渐落地，在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义，带你走近微前端框架，揭秘那些“不为人知”的巧妙技术实现。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;概念&lt;/h2&gt;
 &lt;p&gt;什么是微前端呢？虽然它在2016年就被提出，但是直至今天，我们仍然只能描述它的轮廓，无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;微前端是一种类似于微服务的架构，它将微服务的理念应用于浏览器端，即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时，它们也可以进行并行开发。——《前端架构：从入门到微前端》&lt;/li&gt;
  &lt;li&gt;微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域，做他们关注和专注的事情。团队是跨职能的，从数据库到用户界面开发端到端的功能。——译，micro-frontends.org&lt;/li&gt;
  &lt;li&gt;微前端的核心价值在于 “技术栈无关”，这才是它诞生的理由，或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者，2020.11.20晚阿里云微前端线下沙龙&lt;/li&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;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;table border="0" cellpadding="0" cellspacing="0" width="598"&gt;

  &lt;tr&gt;
   &lt;td width="75"&gt;方案类型&lt;/td&gt;
   &lt;td width="107"&gt;典型技术&lt;/td&gt;
   &lt;td width="106"&gt;优点&lt;/td&gt;
   &lt;td width="160"&gt;缺点&lt;/td&gt;
   &lt;td width="148"&gt;共同点&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;接口协议&lt;/td&gt;
   &lt;td width="107"&gt;single-spa&lt;/td&gt;
   &lt;td width="106"&gt;比较自由，可自主封装&lt;/td&gt;
   &lt;td width="160"&gt;无法满足很多场景&lt;/td&gt;
   &lt;td height="102" rowspan="3" width="148"&gt;
    &lt;ul&gt;
     &lt;li&gt;子应用/模块互不干涉      &lt;br /&gt;
&lt;/li&gt;
     &lt;li&gt;技术栈无关&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;沙箱隔离&lt;/td&gt;
   &lt;td width="107"&gt;qiankun&lt;/td&gt;
   &lt;td width="106"&gt;开发思维简单直接&lt;/td&gt;
   &lt;td width="160"&gt;沙箱带来的性能等问题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;模块协议&lt;/td&gt;
   &lt;td width="107"&gt;webpack module federation&lt;/td&gt;
   &lt;td width="106"&gt;用模块思维理解引用&lt;/td&gt;
   &lt;td width="160"&gt;脱离构建工具无法使用&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;就目前市面上的情况而言，基于沙箱隔离的微前端方案占据了主导，也就是本文将要深入阐述的微前端框架们，也都是这类方案。其中原因，笔者认为最主要的一点，是基于沙箱隔离的方案可以让应用以最小的成本，从原本的单体大应用迁移到微前端架构上来。&lt;/p&gt;
 &lt;h2&gt;微前端框架对比评测&lt;/h2&gt;
 &lt;p&gt;微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎，市面上有非常多的微前端框架，笔者在2021年做过一次收集，比较有典型意义。（虽然在那之后还出现了新的微前端框架，但其大部分原理一致，因此，以下这些框架足以说明情况。）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/phodal/mooa"&gt;Mooa&lt;/a&gt;：基于Angular的微前端服务框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://single-spa.js.org/"&gt;Single-Spa&lt;/a&gt;：最早的微前端框架，兼容多种前端技术栈。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/umijs/qiankun"&gt;Qiankun&lt;/a&gt;：基于Single-Spa，阿里系开源微前端框架。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/ice-lab/icestark"&gt;Icestark&lt;/a&gt;：阿里飞冰微前端框架，兼容多种前端技术栈&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/aliyun/alibabacloud-console-os"&gt;console-os&lt;/a&gt; 是在阿里云控制台体系中孵化的微前端方案， 定位是面向企业级的微前端体系化解决方案。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;Module Federation&lt;/a&gt;：webpack给出的微前端方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/SAP/luigi"&gt;Luigi&lt;/a&gt;：一套复杂的分布式前端应用解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/frintjs/frint"&gt;FrintJS&lt;/a&gt;：自主解决依赖的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/puzzle-js/puzzle-js"&gt;PuzzleJS&lt;/a&gt;：一套复杂的前后端编译时相结合的微前端解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/worktile/ngx-planet"&gt;ngx-planet&lt;/a&gt;：基于angular的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://npmjs.com/package/mfy"&gt;麦饭（mfy）&lt;/a&gt;：精巧简易的微前端框架&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617257866_18_w1023_h537/" rel="attachment wp-att-12266"&gt;   &lt;img alt="" height="537" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" width="1023"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除了webpack的联邦模块方案需要结合构建来做，比较特殊外，其他方案都是在运行时完成应用聚合。&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;a href="https://github.com/tangshuang/mfy"&gt;麦饭&lt;/a&gt;”也属于此类，因此，本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是  &lt;strong&gt;资源加载&lt;/strong&gt;和  &lt;strong&gt;环境隔离&lt;/strong&gt;两大问题，此外，还有路由、通信等问题。&lt;/p&gt;
 &lt;h3&gt;资源加载&lt;/h3&gt;
 &lt;p&gt;微前端框架需要从服务端拉取子应用的代码文件，并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案，现在常见的有两种方案，分别是：以JS文件作为入口；以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本，获得JS导出的内容，但是这样，仅能加载脚本资源，无法加载CSS等样式资源。而以HTML文件为入口，则可以通过HTML文件内的文件引用，把对应的所有JS、CSS文件都一起加载，而且，web站点都是以HTML文件作为入口，这也正好可以让子应用的开发者按照web开发的思路来写子应用。&lt;/p&gt;
 &lt;p&gt;笔者在写麦饭这个框架的时候，希望直接引入子应用就能跑，所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件，这个函数可以根据入口文件，解析子应用的全部资源，并做缓存。&lt;/p&gt;
 &lt;h4&gt;解析资源&lt;/h4&gt;
 &lt;p&gt;框架在获得HTML入口文件地址后，通过HTTP请求获得该文件的内容，对内容进行解析，解析时需要做资源树分析，也就是通过HTML读取所有资源文件，比如link, script[src]。在读取资源时，可能还需要读取资源本身又引入的资源。大致逻辑如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617259137_52_w2382_h512/" rel="attachment wp-att-12267"&gt;   &lt;img alt="" height="172" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" width="801"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在解析过程中，还需要根据registerMicroApp（麦饭提供的注册接口）的配置，决定CSS rules怎么处理。解析获得CSS的技巧，是通过 &amp;lt;style&amp;gt;.sheet 读取 CSSStyleSheet 对象，从中抽离出所有CSS样式规则，再按配置逻辑生成最终的样式规则。&lt;/p&gt;
 &lt;h4&gt;预加载/懒加载&lt;/h4&gt;
 &lt;p&gt;在设计上，一个子应用的资源有两种可选加载形式。在麦饭中，假如你希望提前预加载子应用资源，可以在registerMicroApp时直接传入 importSource(…)，这个函数一执行，就会去请求资源回来并做缓存。但是，假如你不需要预加载，你想在子应用需要进入界面时（或打算让子应用进入界面时）才加载资源，则配置为 () =&amp;gt; importSource(…) ，这种配置会在子应用执行 bootstrap 的时候才去请求资源。&lt;/p&gt;
 &lt;h3&gt;环境隔离&lt;/h3&gt;
 &lt;p&gt;环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的，两个子应用之间，可能存在相互污染的问题，这就要求微前端框架实现一种能力，让子应用运行在自己的一个隔离环境中，从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;iframe：样式和脚本运行的隔离，缺点在于无法全屏弹出层&lt;/li&gt;
  &lt;li&gt;ShadowDOM：样式隔离，缺点在于弹出层被挂在document.body下面，而样式被放在ShadowDOM内部，无法正确渲染弹出层&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;h4&gt;快照沙箱&lt;/h4&gt;
 &lt;p&gt;多个子应用在页面上相互切换，而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png"&gt;   &lt;img alt="" height="206" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这种方案只适合同一时间只运行一个子应用的场景，例如腾讯云控制台。当子应用进入界面的时候，给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时，把window清理干净，再把快照上的属性重新添加到window上，复原了子应用挂载前的window。&lt;/p&gt;
 &lt;h4&gt;代理沙箱&lt;/h4&gt;
 &lt;p&gt;代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1. 创建代理对象&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;比如上面提到window可能被污染。那就创建一个window的代理对象，例如fakeWin，实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617258598_52_w1254_h333/" rel="attachment wp-att-12270"&gt;   &lt;img alt="" height="212" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" width="799"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这样处理之后，我们在读取时可能读取到原始window上的值，但是一旦我们写入新属性之后，再读就读到刚才写入的值，但对于原始的window来说，没有被污染。&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;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617258785_24_w1254_h272/" rel="attachment wp-att-12271"&gt;   &lt;img alt="" height="174" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上面代码里面的window, document, location等，都是前面创建好的代理对象。&lt;/p&gt;
 &lt;p&gt;当然，这里只给出了一些最核心思路的代码，实际上在真正实现时，还要考虑各种特殊情况，需要进行多方面的处理。&lt;/p&gt;
 &lt;p&gt;通过代理沙箱，子应用就可以在主应用中独立运行，而不会对主应用上的其他子应用产生负面影响。不过，值得一提的是，由于代理沙箱实际上虚拟了一个给子应用的环境来运行，也就意味着需要消耗更多的计算资源，会给子应用的性能带来一定影响。同时，由于这种虚拟环境在某些情况下必须连接到真实环境进行操作，或者从另外一面反过来说，虚拟环境中不一定能提供子应用所需要的全部依赖，这就会导致子应用中某些功能失效，甚至影响整个子应用的表现效果。&lt;/p&gt;
 &lt;h3&gt;路由映射&lt;/h3&gt;
 &lt;p&gt;如果子应用有自己的路由系统，处理不好，子应用在切换路由时会污染父应用，导致浏览器url发生变化，结果把当前页面切到另外一个地方去了。为了解决这种问题，麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的，所以，不同层的应用得到的location是不同的，基座应用使用浏览器的location，但是它的子应用则不是，修改浏览器的url之后，可以通过路由映射机制，伪造子应用得到的url。具体实现是通过创建一个临时的iframe，利用代理沙箱的能力，将子应用的location代理到iframe里面的location上去。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617259805_15_w2324_h1116/" rel="attachment wp-att-12274"&gt;   &lt;img alt="" height="334" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" width="696"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;得益于代理沙箱，子应用的url变化不会导致浏览器的url变化。&lt;/p&gt;
 &lt;p&gt;映射逻辑需要写一个map和reactive配置项，当浏览器的url发生变化时，通过map映射到子应用内部。子应用内部url发生变化时，通过reactive映射到浏览器，这样即使用户在某一时刻刷新浏览器，也可以通过url映射关系，准确还原子应用当前的界面。&lt;/p&gt;
 &lt;h3&gt;挂载&lt;/h3&gt;
 &lt;p&gt;在麦饭中，子应用需要通过一个 &amp;lt;mfy-app&amp;gt; 标签来决定子应用挂载在什么地方。和qiankun等框架不同，qiankun需要在子应用中决定挂载点，但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以，子应用在哪里挂载应该由父应用决定。&lt;/p&gt;
 &lt;p&gt;子应用被放在 &amp;lt;mfy-app&amp;gt; 中，给了开发者一些特殊的能力：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;可以放在 v-if 内部，DOM节点被移除后挂回来，子应用还在&lt;/li&gt;
  &lt;li&gt;动画效果&lt;/li&gt;
  &lt;li&gt;keepAlive&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在实现 &amp;lt;mfy-app&amp;gt; 时用到了一些比较 hack 的技巧。比如需要借助 &amp;lt;mfy-app&amp;gt; 这个节点所在作用域的顶层节点，在顶层节点DOM对象上挂载一些数据，通过这个技巧，确保节点被移除后，再被挂载回来时，还能正确还原之前界面。&lt;/p&gt;
 &lt;p&gt;keepAlive则是在 &amp;lt;mfy-app&amp;gt; 节点没有被移除的情况下，子应用执行 unmount 时，并没有实际销毁子应用构建的 DOM 树，而是放在内存中，当子应用再次 mount 的时候，直接把这个内存里面的 DOM 树挂载到 &amp;lt;mfy-app&amp;gt; 内部。&lt;/p&gt;
 &lt;h3&gt;通信/应用树&lt;/h3&gt;
 &lt;p&gt;这部分是麦饭设计中最复杂的部分，也是最终与其他微前端框架区别的地方。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617260472_57_w1206_h1082/" rel="attachment wp-att-12275"&gt;   &lt;img alt="" height="552" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" width="615"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;我构建了一个这样的树状数据结构，称之为“应用树”。它表达了基于 MFY 开发的微前端应用中，应用于子应用的引用关系。&lt;/p&gt;
 &lt;h4&gt;scope&lt;/h4&gt;
 &lt;p&gt;scope概念是指一个应用起来之后，会创建一个scope（作用域），这个 scope 保存了该应用的一些运行时信息，同时通过了通信的接口方法。一个应用可能会有多个子应用，这些子应用都有自己的 scope，上下级应用之间可以通过scope完成通信，比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令，接到这个指令后，两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令，而 parent_app 再把这个指令转发给了 child_app_1，这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。&lt;/p&gt;
 &lt;p&gt;rootScope 是一个特殊的scope，它对应的是基座应用，是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象，所以，利用 rootScope 可以完成跨层应用间的直接通信（虽然不推荐）。&lt;/p&gt;
 &lt;h4&gt;connectScope&lt;/h4&gt;
 &lt;p&gt;每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现，在同一层，实现逻辑有点像react hooks，你不需要关心你处于应用树的哪个位置，对于子应用开发团队而言，只需要在代码中使用connectScope()函数，就可以直接连接到自己所在的作用域。如果你实现过react hooks的话，应该能理解它的一个实现原理。但是由于一些实现上的限制，你不能异步执行connectScope，必须在代码第一次执行时，同步调用connectScope获取当前子应用的scope。&lt;/p&gt;
 &lt;h4&gt;状态共享&lt;/h4&gt;
 &lt;p&gt;“如果子应用1修改了用户的某个状态，子应用2怎么对这个修改做出响应？”&lt;/p&gt;
 &lt;p&gt;这个问题涉及到一个状态共享问题。由于我在设计时，坚持每个子应用团队应该封闭开发的理念，开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用，或者还需要依赖其他应用的状态变化，这会让我在开发的时候一直处于对当前应用状态的未知状态，那这样就没法调试和测试了。因此，设计中我直接拒绝实现子应用间的状态共享。&lt;/p&gt;
 &lt;p&gt;但是在实际使用过程中，这种需求是存在的。因此，我建议使用通信的方式解决，子应用1发出一个消息，通过 rootScope，通知网络我改变了用户状态，那么其他子应用在接受到这个消息之后，自己决定是否要重新渲染界面。&lt;/p&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;子应用自己还要加载资源（angularjs模板）绝对路径问题&lt;/li&gt;
  &lt;li&gt;登录态怎么传递？&lt;/li&gt;
  &lt;li&gt;多语言怎么配置？&lt;/li&gt;
  &lt;li&gt;代码共享（依赖）怎么处理？&lt;/li&gt;
  &lt;li&gt;运行时对象多个怎么办？（例如每个子应用都有自己的jQuery）&lt;/li&gt;
  &lt;li&gt;跨应用加载相同资源怎么办？（例如同时请求一个api拉取数据）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端不是万能的，坑也很多，所以应了那句话“没有银弹”。&lt;/p&gt;
 &lt;h2&gt;结语&lt;/h2&gt;
 &lt;p&gt;微前端是一种架构形式，一旦采用这种架构，就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式，因此，开发团队最好经过比较长一段时间的调研之后，才决定启用这种架构。从本文中，你也会发现，要实现微前端框架的核心能力，需要使用一些看上去不那么优雅的hack方法，既然是hack方法，就存在一定的弊端，比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点，在实际项目中，还需要面临更多问题，但这并不是说我在劝退大家，而是希望大家在选择时，根据实际的需求决定，不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣，可以在文章下面留言，我们一起探讨有关微前端框架的实现技术。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2021/06/22/%e8%a7%85%e8%bf%b9%e5%af%bb%e8%b8%aa%e4%b9%8b%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/cdc-blog-%e5%ba%95%e9%83%a8%e4%ba%8c%e7%bb%b4%e7%a0%81-3/" rel="attachment wp-att-11511"&gt;   &lt;img alt="" height="1020" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2021/06/CDC-Blog-&amp;#24213;&amp;#37096;&amp;#20108;&amp;#32500;&amp;#30721;.png" width="2880"&gt;&lt;/img&gt;&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>技术干货 前端开发 微前端</category>
      <guid isPermaLink="true">https://itindex.net/detail/62121-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</guid>
      <pubDate>Tue, 22 Feb 2022 10:02:42 CST</pubDate>
    </item>
  </channel>
</rss>

