<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[女王控的博客]]></title><description><![CDATA[前端工程师，黑猫女王控，欢迎勾搭，技术相关<a href="https://github.com/towavephone" target="_blank">@towavephone</a>，QQ闲聊<a href="tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=634407147&website=www.oicqzone.com">@towave</a>，bili关注<a href="https://space.bilibili.com/11507708#/" target="_blank">@towave</a>]]></description><link>https://blog.towavephone.com</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 12 Mar 2026 05:57:43 GMT</lastBuildDate><atom:link href="https://blog.towavephone.com/rss.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[接口缓存策略探索]]></title><description><![CDATA[背景 由于有些接口响应时间较长，在接口不需要频繁更新的情况下可以对其进行缓存 实现 cache_tool.py 实现装饰器缓存函数 redis_tool.py 实现删除某个接口的所有缓存结果，这里使用了 redis 的 scan…]]></description><link>https://blog.towavephone.com/interface-cache-strategy-explore/</link><guid isPermaLink="false">https://blog.towavephone.com/interface-cache-strategy-explore/</guid><pubDate>Thu, 30 Oct 2025 11:21:37 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;由于有些接口响应时间较长，在接口不需要频繁更新的情况下可以对其进行缓存&lt;/p&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;p&gt;cache_tool.py&lt;/p&gt;
&lt;p&gt;实现装饰器缓存函数&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;77270258213471860000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import hashlib
import json
import logging
import time

from cacheout import Cache
from controllers.wrapper import get_post_param
from tools import redis_tool

Queue_Cache = Cache(maxsize=1024, ttl=300, timer=time.time, default=None)


def cache_request(is_with_cache_param=False, cache_type=&amp;quot;cache&amp;quot;, ttl=300):
    &amp;quot;&amp;quot;&amp;quot;
    _summary_.

    Args:
        is_with_cache_param (bool, optional): 是否携带缓存参数，默认为 False
    &amp;quot;&amp;quot;&amp;quot;

    def wrapper2(func):
        if cache_type == &amp;quot;cache&amp;quot;:
            queue_cache = Cache(maxsize=1024, ttl=ttl, timer=time.time, default=None)

        def wrapper1(*args, **kwargs):
            request = args[0]
            is_not_cache = get_post_param(
                request, &amp;quot;is_not_cache&amp;quot;, default=&amp;quot;0&amp;quot;, type=int
            )

            # 如果携带缓存参数为真且 is_not_cache 为真，则不使用缓存
            # 除了以上情况，默认使用缓存
            if is_with_cache_param and is_not_cache:
                result = func(*args, **kwargs)

            prefix = f&amp;quot;{func.__module__}.{func.__name__}:&amp;quot;
            key = prefix + hashlib.md5(str(request.POST.dict()).encode()).hexdigest()
            logging.debug(
                &amp;quot;cache_request key %s, params %s&amp;quot;,
                key,
                str(request.POST.dict()).encode(&amp;quot;utf-8&amp;quot;),
            )

            result = None

            if cache_type == &amp;quot;cache&amp;quot;:
                result = queue_cache.get(key)
            elif cache_type == &amp;quot;redis&amp;quot;:
                cache_result = redis_tool.get(key)
                if cache_result:
                    result = json.loads(cache_result)

            if not result:
                result = func(*args, **kwargs)

                if cache_type == &amp;quot;cache&amp;quot;:
                    queue_cache.set(key, result)
                elif cache_type == &amp;quot;redis&amp;quot;:
                    redis_tool.setex(key, ttl, json.dumps(result))

            return result

        return wrapper1

    return wrapper2`, `77270258213471860000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; hashlib
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; json
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; logging
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; cacheout &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Cache
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; controllers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wrapper &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; get_post_param
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; tools &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; redis_tool

Queue_Cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Cache&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maxsize&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timer&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cache_request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;is_with_cache_param&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cache_type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
    _summary_.

    Args:
        is_with_cache_param (bool, optional): 是否携带缓存参数，默认为 False
    &quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            queue_cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Cache&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maxsize&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ttl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timer&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            is_not_cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_post_param&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;is_not_cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;# 如果携带缓存参数为真且 is_not_cache 为真，则不使用缓存&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# 除了以上情况，默认使用缓存&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; is_with_cache_param &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; is_not_cache&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__module__&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__name__&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&quot;&lt;/span&gt;&lt;/span&gt;
            key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; prefix &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; hashlib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hexdigest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;cache_request key %s, params %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queue_cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;redis&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                cache_result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; redis_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; cache_result&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loads&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache_result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cache&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                    queue_cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;redis&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                    redis_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ttl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dumps&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper1

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper2&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;redis_tool.py&lt;/p&gt;
&lt;p&gt;实现删除某个接口的所有缓存结果，这里使用了 redis 的 scan 方法，因为是顺序扫描效率很低，数据量大的时候比较慢&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15285492858620709000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@retry_on_redis_error()
def scan(pattern, count=100000):
    itcl = IntervalTimeCostLogging(func_name=&amp;quot;scan&amp;quot;, enable_call=True)
    cursor = 0
    result = []
    index = 0
    # 这里全量扫描耗时太长，有 20 秒，有 80 万条数据（2025-03-28 16:07:10），暂时只扫描 10 万个
    while True:
        cursor, keys = _get_client().scan(cursor, pattern, count=count)
        itcl.mark(f&amp;quot;scan_from_{index}_to_{index + count}_keys_count_{len(keys)}&amp;quot;)
        result += keys
        #   if cursor == 0 or index == 0:  # 这里临时提前终止
        #       break
        index += count

    itcl.logging_cost()

    logging.debug(f&amp;quot;Scan keys: {result}&amp;quot;)
    return cursor, result

@retry_on_redis_error()
def delete(key: str):
    res = _get_client().delete(key)
    logging.debug(&amp;quot;Delete %s [%s]&amp;quot;, key, res and &amp;quot;hit&amp;quot; or &amp;quot;miss&amp;quot;)
    return res

@retry_on_redis_error()
def delete_scan(pattern):
    _, keys = scan(pattern)
    for key in keys:
        delete(key)`, `15285492858620709000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@retry_on_redis_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    itcl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; IntervalTimeCostLogging&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func_name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;scan&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enable_call&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    cursor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 这里全量扫描耗时太长，有 20 秒，有 80 万条数据（2025-03-28 16:07:10），暂时只扫描 10 万个&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        cursor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _get_client&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scan&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cursor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        itcl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mark&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;scan_from_&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;_to_&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;_keys_count_&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; keys
        &lt;span class=&quot;token comment&quot;&gt;#   if cursor == 0 or index == 0:  # 这里临时提前终止&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;#       break&lt;/span&gt;
        index &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; count

    itcl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;logging_cost&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Scan keys: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cursor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result

@retry_on_redis_error&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _get_client&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Delete %s [%s]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hit&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;miss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res

@retry_on_redis_error&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete_scan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scan&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; keys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;使用时&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;60678413301742576000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# 需要缓存的接口
@request_wrapper()
@cache_tool.cache_request(
    cache_type=&amp;quot;redis&amp;quot;, is_with_cache_param=True
)  # 作用：根据参数缓存响应结果
def get_list(request):
    pass


# 需要删除上面接口的缓存，比如在更新时候需要删除缓存以便拿到最新结果
@request_wrapper()
def update(request):
    # 重置缓存
    redis_tool.delete_scan(
        &amp;quot;接口路径.get_list:*&amp;quot;
    )`, `60678413301742576000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 需要缓存的接口&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@request_wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
@cache_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache_request&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    cache_type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;redis&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_with_cache_param&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 作用：根据参数缓存响应结果&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# 需要删除上面接口的缓存，比如在更新时候需要删除缓存以便拿到最新结果&lt;/span&gt;
&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@request_wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 重置缓存&lt;/span&gt;
    redis_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delete_scan&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;接口路径.get_list:*&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;缓存失效优化&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98%E5%A4%B1%E6%95%88%E4%BC%98%E5%8C%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存失效优化&lt;/h1&gt;
&lt;p&gt;由于上面实现模糊匹配删除缓存 key 即 redis scan 时间过长，需要转换思路通过递增数据版本号来使缓存失效，避免性能问题&lt;/p&gt;
&lt;p&gt;cache_tool.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35895011702435385000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# cache_tool.py（摘取并替换 redis 分支相关部分）
version_key = f&amp;quot;version:{func.__module__}.{func.__name__}&amp;quot;
version = &amp;quot;0&amp;quot;
if cache_type == &amp;quot;redis&amp;quot;:
    v = redis_tool.get(version_key)
    if v:
        version = v.decode() if isinstance(v, bytes) else str(v)

# 旧的 key 生成： prefix + md5(...) 改成带版本
key = f&amp;quot;{prefix}{version}:{hashlib.md5(str(request.POST.dict()).encode()).hexdigest()}&amp;quot;

# 后续正常读写 redis（setex/get）即可`, `35895011702435385000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# cache_tool.py（摘取并替换 redis 分支相关部分）&lt;/span&gt;
version_key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;version:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__module__&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__name__&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; cache_type &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;redis&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; redis_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;version_key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; v&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; v&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 旧的 key 生成： prefix + md5(...) 改成带版本&lt;/span&gt;
key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;prefix&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;hashlib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hexdigest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 后续正常读写 redis（setex/get）即可&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;redis_tool.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72564745327236555000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@retry_on_redis_error()
def incr(key: str):
    # 返回递增后的 int 值
    return _get_client().incr(key)`, `72564745327236555000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@retry_on_redis_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;incr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 返回递增后的 int 值&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; _get_client&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;incr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;使用时&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32470108274225275000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@request_wrapper()
def update(request):
    # ... 执行更新数据库的操作 ...

    # 旧做法：redis_tool.delete_scan(&amp;quot;接口路径.get_list:*&amp;quot;)  # 慢
    # 新做法：递增版本号
    redis_tool.incr(&amp;quot;version:接口路径.get_list&amp;quot;)`, `32470108274225275000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@request_wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ... 执行更新数据库的操作 ...&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 旧做法：redis_tool.delete_scan(&quot;接口路径.get_list:*&quot;)  # 慢&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 新做法：递增版本号&lt;/span&gt;
    redis_tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;incr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;version:接口路径.get_list&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;其他&quot;&gt;&lt;a href=&quot;#%E5%85%B6%E4%BB%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;其他&lt;/h1&gt;
&lt;h2 id=&quot;耗时打印&quot;&gt;&lt;a href=&quot;#%E8%80%97%E6%97%B6%E6%89%93%E5%8D%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;耗时打印&lt;/h2&gt;
&lt;p&gt;实现耗时日志的打印，方便打印每块代码区间的耗时&lt;/p&gt;
&lt;h3 id=&quot;实现-1&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h3&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72730602100230660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import logging
import time

from pydash import py_


class IntervalTimeCostLogging:
    def __init__(
        self,
        enable=False,
        enable_call=False,
        enable_cost=False,
        func_name=&amp;quot;&amp;quot;,
    ):
        self.enable_call = enable_call
        self.enable_cost = enable_cost

        if enable:
            self.enable_call = True
            self.enable_cost = True

        self.func_name = func_name
        self.last_time = time.time()
        self.total_time = 0
        self.log_list = []

    def mark(self, name):
        if not self.enable_call and not self.enable_cost:
            return

        current_time = time.time()
        elapsed_ms = (current_time - self.last_time) * 1000
        self.total_time += elapsed_ms
        log = f&amp;quot;函数名称: {self.func_name}, 区间 {name} 耗时: {elapsed_ms:.2f}ms, 累积耗时: {self.total_time:.2f}ms&amp;quot;
        if self.enable_call:
            logging.debug(log)
        self.log_list.append(
            {
                &amp;quot;name&amp;quot;: name,
                &amp;quot;elapsed_ms&amp;quot;: elapsed_ms,
                &amp;quot;total_time&amp;quot;: self.total_time,
            }
        )
        self.last_time = current_time

    def logging_cost(self):
        if not self.enable_cost:
            return
        from tabulate import tabulate

        self.log_list.sort(key=lambda x: x[&amp;quot;elapsed_ms&amp;quot;], reverse=True)
        data = []
        for item in self.log_list:
            data.append([item[&amp;quot;name&amp;quot;], item[&amp;quot;elapsed_ms&amp;quot;], item[&amp;quot;total_time&amp;quot;]])
        table = tabulate(
            data,
            [&amp;quot;区间名称&amp;quot;, &amp;quot;区间耗时(ms)&amp;quot;, &amp;quot;累积耗时(ms)&amp;quot;],
            tablefmt=&amp;quot;grid&amp;quot;,
            numalign=&amp;quot;right&amp;quot;,
        )
        logging.debug(f&amp;quot;函数名称：{self.func_name} 花费时间按区间耗时倒序: \n{table}&amp;quot;)


def test():
    timer = IntervalTimeCostLogging(enable=True, func_name=&amp;quot;test&amp;quot;)

    # 模拟一些操作
    time.sleep(0.1)
    timer.mark(&amp;quot;step_1&amp;quot;)

    time.sleep(0.2)
    timer.mark(&amp;quot;step_2&amp;quot;)

    time.sleep(0.3)
    timer.mark(&amp;quot;step_3&amp;quot;)

    timer.logging_cost()


if __name__ == &amp;quot;__main__&amp;quot;:
    logging.basicConfig(
        level=logging.DEBUG,
        format=&amp;quot;%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s&amp;quot;
    )
    test()`, `72730602100230660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; logging
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pydash &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; py_


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntervalTimeCostLogging&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        enable&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        enable_call&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        enable_cost&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        func_name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_call &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; enable_call
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_cost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; enable_cost

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; enable&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_call &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
            self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_cost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func_name
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_call &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_cost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

        current_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        elapsed_ms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current_time &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total_time &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; elapsed_ms
        log &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;函数名称: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, 区间 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; 耗时: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;elapsed_ms&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token format-spec&quot;&gt;.2f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ms, 累积耗时: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total_time&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token format-spec&quot;&gt;.2f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ms&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_call&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;elapsed_ms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; elapsed_ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;total_time&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current_time

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;logging_cost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enable_cost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; tabulate &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; tabulate

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;elapsed_ms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reverse&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_list&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;elapsed_ms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;total_time&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tabulate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;区间名称&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;区间耗时(ms)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;累积耗时(ms)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            tablefmt&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;grid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            numalign&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;right&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;函数名称：&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; 花费时间按区间耗时倒序: \n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;table&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; IntervalTimeCostLogging&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;enable&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; func_name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 模拟一些操作&lt;/span&gt;
    time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    timer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mark&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;step_1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    timer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mark&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;step_2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    timer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mark&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;step_3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    timer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;logging_cost&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;basicConfig&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        level&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    test&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;打印效果&quot;&gt;&lt;a href=&quot;#%E6%89%93%E5%8D%B0%E6%95%88%E6%9E%9C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;打印效果&lt;/h3&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18039438096857197000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`2025-10-30 15:31:15,733 DEBUG cost.py:36 函数名称: test, 区间 step_1 耗时: 107.24ms, 累积耗时: 107.24ms
2025-10-30 15:31:15,950 DEBUG cost.py:36 函数名称: test, 区间 step_2 耗时: 216.73ms, 累积耗时: 323.98ms
2025-10-30 15:31:16,251 DEBUG cost.py:36 函数名称: test, 区间 step_3 耗时: 301.41ms, 累积耗时: 625.39ms
2025-10-30 15:31:16,258 DEBUG cost.py:61 函数名称：test 花费时间按区间耗时倒序: 
+--------+------------+------------+
| 区间名称   |   区间耗时(ms) |   累积耗时(ms) |
+========+============+============+
| step_3 |    301.413 |    625.391 |
+--------+------------+------------+
| step_2 |    216.735 |    323.978 |
+--------+------------+------------+
| step_1 |    107.244 |    107.244 |
+--------+------------+------------+`, `18039438096857197000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-10-30 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:31:15,733 DEBUG cost.py:36 函数名称: test, 区间 step_1 耗时: &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt;.24ms, 累积耗时: &lt;span class=&quot;token number&quot;&gt;107&lt;/span&gt;.24ms
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-10-30 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:31:15,950 DEBUG cost.py:36 函数名称: test, 区间 step_2 耗时: &lt;span class=&quot;token number&quot;&gt;216&lt;/span&gt;.73ms, 累积耗时: &lt;span class=&quot;token number&quot;&gt;323&lt;/span&gt;.98ms
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-10-30 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:31:16,251 DEBUG cost.py:36 函数名称: test, 区间 step_3 耗时: &lt;span class=&quot;token number&quot;&gt;301&lt;/span&gt;.41ms, 累积耗时: &lt;span class=&quot;token number&quot;&gt;625&lt;/span&gt;.39ms
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-10-30 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:31:16,258 DEBUG cost.py:61 函数名称：test 花费时间按区间耗时倒序: 
+--------+------------+------------+
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; 区间名称   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   区间耗时&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   累积耗时&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;+
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; step_3 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;301.413&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;625.391&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
+--------+------------+------------+
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; step_2 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;216.735&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;323.978&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
+--------+------------+------------+
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; step_1 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;107.244&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;    &lt;span class=&quot;token number&quot;&gt;107.244&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
+--------+------------+------------+&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[前端多平台页面适配]]></title><description><![CDATA[背景 网页在不同平台上展示的布局不一致，需要改成类似等比缩放的效果以便在各平台显示 选型 主流方案 官网方案 实现 核心逻辑 使用 postcss-px-to-viewport 插件，将 src 以及三方库(node_modules)下的所有样式文件   里面的 px…]]></description><link>https://blog.towavephone.com/frontend-multi-platform-page-adaptation/</link><guid isPermaLink="false">https://blog.towavephone.com/frontend-multi-platform-page-adaptation/</guid><pubDate>Mon, 27 Oct 2025 11:43:35 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;网页在不同平台上展示的布局不一致，需要改成类似等比缩放的效果以便在各平台显示&lt;/p&gt;
&lt;h1 id=&quot;选型&quot;&gt;&lt;a href=&quot;#%E9%80%89%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;选型&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/mobile-page-adaptation/&quot;&gt;主流方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/international-official-website-technical-difficulties/#%E5%A4%9A%E5%B9%B3%E5%8F%B0%E9%80%82%E9%85%8D&quot;&gt;官网方案&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;h2 id=&quot;核心逻辑&quot;&gt;&lt;a href=&quot;#%E6%A0%B8%E5%BF%83%E9%80%BB%E8%BE%91&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;核心逻辑&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 postcss-px-to-viewport 插件，将 src 以及三方库(node_modules)下的所有样式文件 &lt;code class=&quot;language-text&quot;&gt;(*.css|*.module.css|*.less|*.module.less)&lt;/code&gt; 里面的 px 单位转换到 rem 单位&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在公共样式下根据媒体查询（屏幕宽度 or 屏幕 dpi）设置每种字体的大小&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由于目前插件不能转换非样式文件里面的 px 单位，所以需要写两种公共样式，这里会通过 url 来决定哪种生效&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要等比缩放的，根据屏幕宽度改变字体大小（新页面，即此次重构页面）&lt;/li&gt;
&lt;li&gt;需要固定大小的，固定设置为 1920 下的字体大小（老页面）&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;重构目标：重构页面采用等比缩放，老页面维持固定大小，注意上线之后所有非样式代码即 js 文件里面禁止使用 px 单位，需要手动转换为 rem 单位，转换公式为 px 大小 / 10&lt;/p&gt;
&lt;p&gt;最终目标：所有页面统一采用等比缩放 （即将 js 代码里面的 px 单位全部改为 rem 单位，三方库里面的 js 代码暂不考虑，之后才能全部采用此方案）&lt;/p&gt;
&lt;h2 id=&quot;postcssconfigjs（插件转换-px-为-rem）&quot;&gt;&lt;a href=&quot;#postcssconfigjs%EF%BC%88%E6%8F%92%E4%BB%B6%E8%BD%AC%E6%8D%A2-px-%E4%B8%BA-rem%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;postcss.config.js（插件转换 px 为 rem）&lt;/h2&gt;
&lt;h3 id=&quot;配置分离&quot;&gt;&lt;a href=&quot;#%E9%85%8D%E7%BD%AE%E5%88%86%E7%A6%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;配置分离&lt;/h3&gt;
&lt;p&gt;由于想让 cra 使用 postcss.config.js 的配置（cra 默认不使用），在 cra 5 版本和 customize-cra 情况下，addPostcssPlugins &lt;a href=&quot;https://github.com/arackaf/customize-cra/issues/327#issuecomment-1210459104&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;不起作用&lt;/a&gt;，有 2 种方式可以解决&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;重写 addPostcssPlugins&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72178081005912320000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const addPostcssPlugins = (plugins) =&gt; (config) =&gt; {
  const rules = config.module.rules.find((rule) =&gt; Array.isArray(rule.oneOf)).oneOf;
  rules.forEach(
     (r) =&gt;
        r.use &amp;&amp;
        r.use.forEach((u) =&gt; {
           if (u.options &amp;&amp; u.options.ident === &apos;postcss&apos;) {
              const postcssOptions = {
                 ident: &apos;postcss&apos;,
                 config: false
              };
              if (!u.options.plugins) {
                 // u.options.plugins = () =&gt; [...plugins]
                 postcssOptions.plugins = plugins;
              } else {
                 // const originalPlugins = u.options.plugins
                 // u.options.plugins = () =&gt; [...originalPlugins(), ...plugins]
                 postcssOptions.plugins = plugins;
              }

              delete u.options.plugins;
              delete u.options.ident;

              u.options.postcssOptions = postcssOptions;
           } else if (u.options &amp;&amp; u.options.postcssOptions &amp;&amp; u.options.postcssOptions.ident === &apos;postcss&apos;) {
              if (!u.options.postcssOptions.plugins) {
                 u.options.postcssOptions.plugins = plugins;
              } else {
                 const originalPlugins = u.options.postcssOptions.plugins;
                 u.options.postcssOptions.plugins = [...originalPlugins, ...plugins];
              }
           }
        })
  );
  return config;
};`, `72178081005912320000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;addPostcssPlugins&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oneOf&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oneOf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  rules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;use &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;use&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postcss&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postcssOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 ident&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postcss&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                 config&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;token comment&quot;&gt;// u.options.plugins = () =&gt; [...plugins]&lt;/span&gt;
                 postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;token comment&quot;&gt;// const originalPlugins = u.options.plugins&lt;/span&gt;
                 &lt;span class=&quot;token comment&quot;&gt;// u.options.plugins = () =&gt; [...originalPlugins(), ...plugins]&lt;/span&gt;
                 postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

              &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

              u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postcss&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; originalPlugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                 u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;originalPlugins&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;去掉相关配置，&lt;a href=&quot;https://github.com/arackaf/customize-cra/issues/327#issuecomment-1371815281&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;依赖 postcss.config.js&lt;/a&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;76872458604242570000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const enablePostcssRc = () =&gt; (config) =&gt; {
  const rules = config.module.rules.find((rule) =&gt; Array.isArray(rule.oneOf)).oneOf;
  rules.forEach(
     (r) =&gt;
        r.use &amp;&amp;
        r.use.forEach((u) =&gt; {
           if (u.options &amp;&amp; u.options.ident === &apos;postcss&apos;) {
              delete u.options.ident;
              if (u.options.plugins) {
                 delete u.options.plugins; // 删除 plugins 属性，使用 postcss.config.js 中的配置
              }
           }

           if (u.options &amp;&amp; u.options.postcssOptions &amp;&amp; u.options.postcssOptions.ident === &apos;postcss&apos;) {
              delete u.options.postcssOptions;
           }
        })
  );
  return config;
};`, `76872458604242570000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;enablePostcssRc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rules &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oneOf&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oneOf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  rules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;use &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;use&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postcss&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 删除 plugins 属性，使用 postcss.config.js 中的配置&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

           &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ident &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postcss&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postcssOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;采用方案二&lt;/p&gt;
&lt;h3 id=&quot;rspack-适配&quot;&gt;&lt;a href=&quot;#rspack-%E9%80%82%E9%85%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;rspack 适配&lt;/h3&gt;
&lt;p&gt;rule 需要添加以下规则来兼容业务代码/三方库的样式文件&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;4794120838269267000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
   module: {
      rules: [
         {
            test: /\.css\$/,
            exclude: /\.module\.css\$/,
            use: [&apos;postcss-loader&apos;],
            type: &apos;css&apos;
         },
         {
            test: /\.module\.css\$/,
            use: [&apos;postcss-loader&apos;],
            type: &apos;css/module&apos;
         },
         {
            test: /\.less\$/,
            exclude: /\.module\.less\$/,
            use: [
               &apos;postcss-loader&apos;,
               {
                  loader: &apos;less-loader&apos;,
                  options: {
                     lessOptions: { javascriptEnabled: true }
                  }
               }
            ],
            type: &apos;css&apos;
         },
         {
            test: /\.module\.less\$/,
            use: [
               &apos;postcss-loader&apos;,
               {
                  loader: &apos;less-loader&apos;,
                  options: {
                     lessOptions: { javascriptEnabled: true }
                  }
               }
            ],
            type: &apos;css/module&apos;
         }
      ];
   }
}`, `4794120838269267000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   module&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      rules&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            test&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.css$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            exclude&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.module\.css$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            use&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;postcss-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;css&apos;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            test&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.module\.css$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            use&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;postcss-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;css/module&apos;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            test&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.less$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            exclude&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.module\.less$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            use&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&apos;postcss-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  loader&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;less-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  options&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                     lessOptions&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; javascriptEnabled&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;css&apos;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            test&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;/\.module\.less$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            use&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&apos;postcss-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  loader&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;less-loader&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  options&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                     lessOptions&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; javascriptEnabled&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;css/module&apos;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;配置代码&quot;&gt;&lt;a href=&quot;#%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%A0%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;配置代码&lt;/h3&gt;
&lt;p&gt;postcss.config.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1188233310584818700&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`module.exports = {
   plugins: [
      &apos;postcss-flexbugs-fixes&apos;,
      [
         &apos;postcss-preset-env&apos;,
         {
            autoprefixer: {
               flexbox: &apos;no-2009&apos;
            },
            stage: 3
         }
      ],
      &apos;postcss-normalize&apos;,
      [
         &apos;postcss-px-to-viewport&apos;,
         {
            // https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md
            unitToConvert: &apos;px&apos;, // 需要转换的单位，默认为&amp;quot;px&amp;quot;
            viewportWidth: 1000, // 视窗的宽度，对应设计稿的宽度
            unitPrecision: 8, // 指定 px 转换为视窗单位值的小数后 x 位数，转换精度尽可能的大，防止出现图片比例问题
            viewportUnit: &apos;rem&apos;, // 希望使用的视口单位
            fontViewportUnit: &apos;rem&apos;, // 字体使用的视口单位
            minPixelValue: 1, // 最小的转换数值
            mediaQuery: true, // 媒体查询里的单位是否需要转换单位
            selectorBlackList: [/^html\$/, &apos;hack&apos;] // 需要忽略的 CSS 选择器，不会转为视口单位，使用原有的 px 等单位
            // include: /\/src\//
            // exclude: /node_modules/,
         }
      ]
   ]
};`, `1188233310584818700`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   plugins&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;postcss-flexbugs-fixes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;postcss-preset-env&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            autoprefixer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               flexbox&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;no-2009&apos;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            stage&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;postcss-normalize&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;postcss-px-to-viewport&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md&lt;/span&gt;
            unitToConvert&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;px&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 需要转换的单位，默认为&quot;px&quot;&lt;/span&gt;
            viewportWidth&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 视窗的宽度，对应设计稿的宽度&lt;/span&gt;
            unitPrecision&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 指定 px 转换为视窗单位值的小数后 x 位数，转换精度尽可能的大，防止出现图片比例问题&lt;/span&gt;
            viewportUnit&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;rem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 希望使用的视口单位&lt;/span&gt;
            fontViewportUnit&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;rem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 字体使用的视口单位&lt;/span&gt;
            minPixelValue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 最小的转换数值&lt;/span&gt;
            mediaQuery&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 媒体查询里的单位是否需要转换单位&lt;/span&gt;
            selectorBlackList&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/^html$/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hack&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 需要忽略的 CSS 选择器，不会转为视口单位，使用原有的 px 等单位&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// include: /\/src\//&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// exclude: /node_modules/,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;commonless（缩放规则）&quot;&gt;&lt;a href=&quot;#commonless%EF%BC%88%E7%BC%A9%E6%94%BE%E8%A7%84%E5%88%99%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;common.less（缩放规则）&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5257905429440801000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 固定大小的页面
.hackFixedPage {
   font-size: 10px;
}

// 等比缩放的页面
.hackResponsivePage {
   // 大于 1500px (10px)
   @media screen and (min-width: 1501px) {
      font-size: 10px;
   }

   // 1300px～1500px 的宽度区间的根字号大小是某个范围（8px～10px）
   @media screen and (min-width: 1300px) and (max-width: 1500px) {
      font-size: calc(8px + 2 * (100vw - 1300px) / 200);
   }

   // 小于 1300px 的屏幕宽度，固定根字号大小（8px）
   @media screen and (max-width: 1299px) {
      font-size: 8px;
   }
}`, `5257905429440801000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;less&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;less&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;less&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-less line-numbers&quot;&gt;&lt;code class=&quot;language-less&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 固定大小的页面&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.hackFixedPage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 等比缩放的页面&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.hackResponsivePage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 大于 1500px (10px)&lt;/span&gt;
   &lt;span class=&quot;token atrule&quot;&gt;@media screen and &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min-width&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1501px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 1300px～1500px 的宽度区间的根字号大小是某个范围（8px～10px）&lt;/span&gt;
   &lt;span class=&quot;token atrule&quot;&gt;@media screen and &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min-width&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1300px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; and &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max-width&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1500px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;8px &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; 2 &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100vw &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 1300px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; 200&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 小于 1300px 的屏幕宽度，固定根字号大小（8px）&lt;/span&gt;
   &lt;span class=&quot;token atrule&quot;&gt;@media screen and &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max-width&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1299px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;实际使用&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E9%99%85%E4%BD%BF%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实际使用&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;9649492146294825000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`.hintIcon {
   font-size: 14px; // 实际转换为 14 / 10 = 1.4rem
   margin-left: 8px; // 实际转换为 8 / 10 = 0.8rem
   vertical-align: 1px; // 不做转换
}`, `9649492146294825000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;less&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;less&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;less&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-less line-numbers&quot;&gt;&lt;code class=&quot;language-less&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.hintIcon&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 14px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 实际转换为 14 / 10 = 1.4rem&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;margin-left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 8px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 实际转换为 8 / 10 = 0.8rem&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;vertical-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 不做转换&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;三方库非样式文件单位转换&quot;&gt;&lt;a href=&quot;#%E4%B8%89%E6%96%B9%E5%BA%93%E9%9D%9E%E6%A0%B7%E5%BC%8F%E6%96%87%E4%BB%B6%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;三方库非样式文件单位转换&lt;/h1&gt;
&lt;p&gt;在上面适配规则上线之后，发现有的三方库不能正确的缩放，导致页面看起来不协调，以下是各个三方库的修复&lt;/p&gt;
&lt;h2 id=&quot;pro-components&quot;&gt;&lt;a href=&quot;#pro-components&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;pro components&lt;/h2&gt;
&lt;p&gt;参考&lt;a href=&quot;https://github.com/ant-design/cssinjs?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;文档&lt;/a&gt;，对应&lt;a href=&quot;https://github.com/ant-design/cssinjs/blob/master/src/transformers/px2rem.ts&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;源码&lt;/a&gt;，可以看出是运行时的单位转换&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37056957003342660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { px2remTransformer, StyleProvider } from &apos;@ant-design/cssinjs&apos;;

const StyleHoc = ({ children }) =&gt; {
   // 和上面的 postcss-px-to-viewport 的适配策略保持一致
   // 以 10 为根字体大小进行转换，保留 8 位小数，媒体查询下也进行转换
   return (
      &lt;StyleProvider transformers={[px2remTransformer({ rootValue: 10, precision: 8, mediaQuery: true })]}&gt;
         {children}
      &lt;/StyleProvider&gt;
   );
};`, `37056957003342660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; px2remTransformer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; StyleProvider &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@ant-design/cssinjs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;StyleHoc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 和上面的 postcss-px-to-viewport 的适配策略保持一致&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 以 10 为根字体大小进行转换，保留 8 位小数，媒体查询下也进行转换&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StyleProvider transformers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;px2remTransformer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; rootValue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; precision&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mediaQuery&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;StyleProvider&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;mui&quot;&gt;&lt;a href=&quot;#mui&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;mui&lt;/h2&gt;
&lt;p&gt;参考&lt;a href=&quot;https://v5.mui.com/material-ui/customization/theming/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;文档&lt;/a&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45627287795641450000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { createTheme, ThemeProvider } from &apos;@mui/material/styles&apos;;

const theme = createTheme({
   // 和上面的 postcss-px-to-viewport 的适配策略保持一致
   // 以 10 为根字体大小进行转换
   typography: {
      pxToRem: (size) =&gt; \`\${size / 10}rem\`
   }
});

const ThemeHoc = ({ children }) =&gt; {
   return &lt;ThemeProvider theme={theme}&gt;{children}&lt;/ThemeProvider&gt;;
};`, `45627287795641450000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createTheme&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ThemeProvider &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@mui/material/styles&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createTheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 和上面的 postcss-px-to-viewport 的适配策略保持一致&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 以 10 为根字体大小进行转换&lt;/span&gt;
   typography&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;pxToRem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;ThemeHoc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ThemeProvider theme&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ThemeProvider&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[React 前端埋点选型]]></title><description><![CDATA[背景 统计线上用户各个功能的使用情况，以便后续针对性的优化 实现 前端 使用  https://github.com/nytimes/react-tracking  三方库埋点 使用 sendBeacon 异步发送埋点信息 埋点信息默认发送 location，type…]]></description><link>https://blog.towavephone.com/react-frontend-tracking-selection/</link><guid isPermaLink="false">https://blog.towavephone.com/react-frontend-tracking-selection/</guid><pubDate>Mon, 27 Oct 2025 11:17:30 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;统计线上用户各个功能的使用情况，以便后续针对性的优化&lt;/p&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;h2 id=&quot;前端&quot;&gt;&lt;a href=&quot;#%E5%89%8D%E7%AB%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;前端&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;a href=&quot;https://github.com/nytimes/react-tracking&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://github.com/nytimes/react-tracking&lt;/a&gt; 三方库埋点&lt;/li&gt;
&lt;li&gt;使用 sendBeacon 异步发送埋点信息&lt;/li&gt;
&lt;li&gt;埋点信息默认发送 location，type（默认为 enter，即进入页面类型），create_by（埋点触发人）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;utils/sendReport.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;98834230392413550000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`function sendBeacon(url, data) {
   if (!navigator.sendBeacon) {
      return false;
   }

   return navigator.sendBeacon(url, data);
}

export default function sendReport(data) {
   const newData = new FormData();
   newData.append(&apos;data&apos;, JSON.stringify(data));
   return sendBeacon(&apos;/web_track/send/&apos;, newData);
}`, `98834230392413550000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sendBeacon&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   newData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/web_track/send/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;index.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40852846146181250000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { pick } from &apos;lodash&apos;

import sendReport from &apos;./utils/sendReport&apos;;

const isProd = process.env.NODE_ENV === &apos;production&apos;;

const TrackHoc = ({ children }) =&gt; {
   const { Track } = useTracking(
      () =&gt; ({
         location: pick(window.location, [&apos;origin&apos;, &apos;hash&apos;, &apos;href&apos;]),
         type: &apos;enter&apos;, // 默认值
         create_by: &apos;当前登录用户或者不传根据 cookie 来判断用户&apos;
      }),
      {
         dispatch: (data) =&gt; {
            console.log(&apos;tracking&apos;, data);
            // 不是正式环境不上传埋点数据
            if (!isProd) {
               return;
            }
            sendReport(data);
         }
      }
   );

   return &lt;Track&gt;{children}&lt;/Track&gt;
};`, `40852846146181250000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pick &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lodash&apos;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; sendReport &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./utils/sendReport&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isProd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;production&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;TrackHoc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Track &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTracking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         location&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;origin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;hash&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;href&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;enter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 默认值&lt;/span&gt;
         create_by&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;当前登录用户或者不传根据 cookie 来判断用户&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function-variable function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tracking&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 不是正式环境不上传埋点数据&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isProd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;sendReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Track&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Track&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;后端&quot;&gt;&lt;a href=&quot;#%E5%90%8E%E7%AB%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;后端&lt;/h2&gt;
&lt;p&gt;数据库表 web_track 记录了以下字段&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户(create_by)&lt;/li&gt;
&lt;li&gt;访问信息(location)&lt;/li&gt;
&lt;li&gt;对应页面(page)&lt;/li&gt;
&lt;li&gt;对应功能(function)&lt;/li&gt;
&lt;li&gt;埋点类型(type，目前有 enter/click 两类，对应进入页面/事件点击)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;使用方式&quot;&gt;&lt;a href=&quot;#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;使用方式&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61467651050133390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { useTracking } from &apos;react-tracking&apos;;

// 在函数组件使用，进入页面触发专用
useTracking({ page: &apos;human_driver_compare&apos; }, { dispatchOnMount: true });

// 在函数组件使用，事件触发专用
const { trackEvent, Track } = useTracking({ page: &apos;job_detail&apos; });
// 这里使用 Track 高阶组件包裹之后，孙子组件调用 trackEvent 时候就会合并父级 data（当然这里的 trackEvent 触发也会合并），即孙子组件不需要在重复 page: &apos;job_detail&apos;，默认 type 为 enter
return &lt;Track&gt;{children}&lt;/Track&gt;;

// 在类组件中使用，装饰器装饰之后 props 就带有 tracking 属性
@track()
export default class Statistic extends React.Component {
   handleShowSummaryClick = (e) =&gt; {
      const { checked } = e.target;
      this.setState({ show_summary: checked });
      if (checked) {
         // 事件触发
         this.props.tracking.trackEvent({ function: &apos;show_summary&apos;, type: &apos;click&apos; });
      }
   };

   // 点击事件触发，即调用这个函数就触发，注意和上面的触发逻辑不同
   @track({ function: &apos;show_summary&apos;, type: &apos;click&apos; })
   handleShowSummaryClick = (e) =&gt; {
      const { checked } = e.target;
      this.setState({ show_summary: checked });
   };
}`, `61467651050133390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useTracking &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react-tracking&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 在函数组件使用，进入页面触发专用&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;useTracking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;human_driver_compare&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; dispatchOnMount&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 在函数组件使用，事件触发专用&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trackEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Track &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useTracking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;job_detail&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 这里使用 Track 高阶组件包裹之后，孙子组件调用 trackEvent 时候就会合并父级 data（当然这里的 trackEvent 触发也会合并），即孙子组件不需要在重复 page: &apos;job_detail&apos;，默认 type 为 enter&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Track&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Track&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 在类组件中使用，装饰器装饰之后 props 就带有 tracking 属性&lt;/span&gt;
@&lt;span class=&quot;token function&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Statistic&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function-variable function&quot;&gt;handleShowSummaryClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; checked &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; show_summary&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; checked &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;checked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 事件触发&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tracking&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;show_summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 点击事件触发，即调用这个函数就触发，注意和上面的触发逻辑不同&lt;/span&gt;
   @&lt;span class=&quot;token function&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;show_summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;click&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token function-variable function&quot;&gt;handleShowSummaryClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; checked &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; show_summary&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; checked &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之后使用 redash 读取 mongo 数据库进行统计&lt;/p&gt;</content:encoded></item><item><title><![CDATA[前端性能测试工具]]></title><description><![CDATA[背景 前端页面需要量化页面性能，以此来说明优化前后的效果 选型 本地 选型 Star 数 最后代码提交时间 优点 缺点 截图 实现效果 备注 Chrome DevTools Performance 面板 无 无 浏览器自带 只能简单的说明性能 Performance API…]]></description><link>https://blog.towavephone.com/frontend-performance-testing-tools/</link><guid isPermaLink="false">https://blog.towavephone.com/frontend-performance-testing-tools/</guid><pubDate>Fri, 24 Oct 2025 11:23:45 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;前端页面需要量化页面性能，以此来说明优化前后的效果&lt;/p&gt;
&lt;h1 id=&quot;选型&quot;&gt;&lt;a href=&quot;#%E9%80%89%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;选型&lt;/h1&gt;
&lt;h2 id=&quot;本地&quot;&gt;&lt;a href=&quot;#%E6%9C%AC%E5%9C%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;本地&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选型&lt;/th&gt;
&lt;th&gt;Star 数&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;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome DevTools Performance 面板&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;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance API（window.performance）&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;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;29.2K&lt;/td&gt;
&lt;td&gt;2025-08-05&lt;/td&gt;
&lt;td&gt;浏览器自带，很快可以出报告&lt;/td&gt;
&lt;td&gt;1. 需要用隐私模式打开排除干扰
&lt;br&gt;
2. CLI/node 版本需要 Node 18&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-15a59.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 143.1767337807606%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAIAAAAl5NuSAAAACXBIWXMAAAsSAAALEgHS3X78AAABtUlEQVQ4y5VUW3LjIBDM/e+233sDZa1IQhIwPAZG2wI75cRxwP0hY4qunul5vEXvgJzzcQ8RwZdo95rY14t6d4+34wnwkL130XFOz948IRtzKIXf4MM4TpdxHN4v5EOTLEjAjqN9v+AQQtyNNZbUpkPktrJzbMy+GKcpH7/iOxmu+Ohz4pL1kVNijuehoK2sttl5X8/JWj1Np805d7kdlsWTvRbLGfXxL6fc57YIkY98fZ0kxczIgJnbYUMQrn4GiL+cko2COiH7NpmMcURVR7LkbUuLiuvaJuO92dQ8zxxjNRy1PrZVNiU9ZBhsrUXLhyQezXkNIUm7VGfYlmAaesXaRPRYod9KZTVKO6/rSlqj1zKz3NBBNlYtSmsNfVgNIJyjk6xP5cnA8zrnhVj5PYbZKuscWfI+ng1etdt15pLk3c31PndukhLngYhZ8gsjedtBp8hA05/l72vkz5hd9Iq218gwubjl+KEf24bFGOsAygPaymgstBcKFkL4QmiTS6lO2bK0pEyW9C/9H5dGFxk0WIWAkXn9YrB9ueHmPIO87zv46NBhGMYPzBft+znh8VwPX/Afjhq2hFcE1oMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 24 13 44 51&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-fee1c.png&quot; data-srcset=&quot;/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-a67b7.png 200w,
/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-0b187.png 400w,
/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-fee1c.png 800w,
/static/2025-10-24-13-44-51-b31c0786cd977a0c88a7ea01d23a00a3-15a59.png 894w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/get-started&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PageSpeed Insights API&lt;/a&gt;
 或者叫 
&lt;a href=&quot;https://github.com/sitespeedio/plugin-gpsi&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Google Page Speed Insights&lt;/a&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;1. 只适用于线上
&lt;br&gt;
2. 需要申请秘钥&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;a href=&quot;https://chromewebstore.google.com/detail/checkbot-seo-web-speed-se/dagohlmlhagincbfilmkadjgmdnkjinl/reviews&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Checkbot&lt;/a&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;只能展示 100 个 URL，超过收费&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-a9fd5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.328125%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABmUlEQVQoz22SyU4bQRCG5x045AKKBxvhmMx4gWPyOLnkkEtu5D04obwFiAM2i0MIwrckiiHIQlZAYDDGy/R0dy1N9QSUQFL6pZZ66qv6p6qDSq1WrVTK5bi5t+ecU9oYQCAyeC9LBA8iwqHhX4Pk4maE7ILq4lKpVCoWi58+7wsMSMSOmXuKTsfYHdPYsuRZ4t9KDNwmaX+UTJQO4jguFOae53KbW9vonLACizLAC8h1h9juw9ENnAwgtXiZ4NkElIEgiuL54vxsGNbrDenMTO5xMDultE7vJaakvrVWPnk4iqJ8GO7s7AoHAE9hsSMF/MkPJKRGRkFBuVJdeBnN5vONrW1JJZ/3FO6n1Eu824nBrAIjSiIHLwrhXG5m+tlUo173MKL7J/6atjAEiAbAw68+fHy9vLr0fqX5raO1NvY/tocarpXXSKP4djKXrEzw5ot713JvD93BmRrdDmTP/Ni5LM6oVMvMRImSXTLKDrLO663jjcP22sGP1tfjo5+d7+2T84ur7K/+hG8jDeWFXPXO+xML/kru7wATlAiymg/8/QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 24 13 46 50&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-fee1c.png&quot; data-srcset=&quot;/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-a67b7.png 200w,
/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-0b187.png 400w,
/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-fee1c.png 800w,
/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-b1a91.png 1200w,
/static/2025-10-24-13-46-50-95bd9b3e28a09ecb8fd18eb07d8db8f8-a9fd5.png 1280w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.checkbot.io/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://www.checkbot.io/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/catchpoint/WebPageTest&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;WebPageTest&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3.2K&lt;/td&gt;
&lt;td&gt;2025-07-14&lt;/td&gt;
&lt;td&gt;测试结果完善，有录屏，有瀑布图&lt;/td&gt;
&lt;td&gt;1. 可能收费
&lt;br&gt;
2. 不支持私有域名&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-1e123.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 154.96368038740923%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAfCAIAAABoLHqZAAAACXBIWXMAAAsSAAALEgHS3X78AAAEuElEQVQ4y6VU2U9cVRif6IOmpj5QKQVaWUPLUhsRKTAijUljE6MPxpYEAg9WjVEfffDZv8TWJmwt2+CwOTOlgFYKbWmhxTDAzDB35u5z93vuOn733mGEkTe/nDnnzD3f7/y+7Xy+vns4jP5R/JsA+W2A7BvBewb2u3/duX4reuN2FOYvfnHmG7eivYPx3qFEjzPveyjf2/6bMCo7v7p47Yema9+f838Jf6s7+mv9/TDXdfY1XOmr6+wtb+k+fenzsubr5e91n23t8VC+U6WNMIpKG4pgLmvyNuU1l6saPjhX529p+/DTT7o6u65UnO+oPN9WXd9RU99+tqbFQ/nOVLedrrxc+85Hzf7PGls+rm++euHdq1WNXcUVLSVVrWW1HSeKm/q+/hFLEevrm6uPnu3EUncGJ96qeB+AvlffrH3lZM3Jksby2tbS2vYzlW3l1f7iqvY3Si69duri60UXfL7im9/9lM1mBYEXRQk2C4t/+k5UAdA3NR3+bSYyOjEzODozMDY3GghNBkPDY3N37w2N3Pl5KjgzOjn3+MnzbNaWZVlRFMs0cJwKBEMA9GX/h/h0w7Bsm2WYJIbxIBwvShJSVQInFAUhpKsq0jWNoWhR4E3T0DRDN0wQmHLMGpJ5LiOIAs2SYJ6GVIZIC3yG5Wg2wwCYY6gMTWc4FlRs08gxewuQO7dZJtIV0zBMy9J0HYxSNQVpqmXZMpJFVVQQvx7Ho7gTNts+AIOiLKuKihQVUMZ/3bNscA8cNDEWYRk1x2zDDdksSWewNJ0mmBROUzQnK6ooKbwoq5rMS5yma+A4UhSWJlmGVWX5CJigWJygUziVSlP7GEGQDMNySYxiBCJJxmRV4kRB4kmRie8mSODImW254PWN6ND4/cm5h7ORtfmFtbnI6kx4dWF5Lbzw18KD1dnwynRoJTT/MLL0ZGlp6enz6BHm3Xhqcyu2tb2/tR3f/BtGLLqHgVICS2xubyQwfDuWiO9he9vPXrzcgqMcc85skgFrkykS7E/jdJpkBUmK7xOCxMXSu+AwyzGSLAtMPJUmQS3HDGmAZfXp1u3h+eGJByOBxbHg4uz072Pj4dHg8sjk4mQwMj4VGp8KD47fHwksD4xFllc2Dsx2F16QkmmKoDLIzS8UEizIKS3d0DXDMHRdc4+cL/lc5vMMh8gpE1eQpsEjAL9gDz9RFOEIHISroGJdh+1DzJKapATwAJQoisKxpCwpXuXJjrh7y6JFDarPi9YhZtOSkAlgsJCkyGQiIQleDdpumRoe2CP891V5C9jmbcA5mqFZnhI43gPkVW1XjgEfKDnPgxdEisY5hrBdL/Iwj/nwFTmwKCsEw3nakkgx9I7EJW33UqQiCLTtPjtWcn0uAMvISGUU3XBCLUlQ/AmRw71oO71HRZ51SDePYYYnDHjdiSREyJIoxtJlz0ynVSgqRUqQ7ALPC3x2tkjJpv4Ia+QL09FxvhvMHvPoLrQDu6CH5cH2ITEN6JKm882C2bQtPWvJedpC5sK+YdlQh1CeSMIQ99hQEl4usscyQ8UybAaS5P0lSDqewKAtGdAtNdWrcrgMBFavZo6YDS0WDnLBVxS4CJTAarcvOlkwDyQfoH8AX58quirQdYkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 24 13 48 21&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-fee1c.png&quot; data-srcset=&quot;/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-a67b7.png 200w,
/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-0b187.png 400w,
/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-fee1c.png 800w,
/static/2025-10-24-13-48-21-67673a4bfecfa116c7ede8777edfdf8d-1e123.png 826w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;
 
&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-f7915.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 825.7328990228012%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAClCAIAAADTfUqKAAAACXBIWXMAAAsSAAALEgHS3X78AAAO1klEQVRo3p1a2Y9k11lv8QhicWwTbM+MJ5FjO55kpLg1YCfOJAoEHi0gjpIYHpwHlLzyBI9I/BNIIfAQTCCL07E9Y5txFBGPkYhsT6+13H3f932pan7nnKrqruru6S6uP9869/b9nW893/ede2fj5f/QQH/9n9rf/Mz8zpaJ8Td+IH79X0cv/csQ9LXvD//ie8O//OfhS98ffvPfhG+9KpLzv0sMtXH5869c+vwrV1/49rWvfvfan3wXl1e+8O1PPP9Xn3z+ZZyf+uK3rv/xy0/ffPmxza9//PqfP/q5r13afOnSjW/iMdDGg49+htK1hy5df/Cxz37skWsPPHLtsU89/8lrNy8/9cIffuHmiy9+6YUvf/nSp567/MSNx5987upTzz3yiU2G2vj9qzc+dmXzietf2fzii5+58WfPbH7105t/evWZmw9e3nzoyubDj//Rb/zup1/5zt9bjjcYcNvbQ1n3fvDDrQcuPfvw1Rsbv/MH13/74599+MqzV558/vGnb15+8uYT175y6ekvPfDYs7/36LMPXv7cxm9d/du/+8fDw8Msy9Ikw+AXv3z/Nx96BsCNN26/++Zbv3jt52//aOudN9/+Jej1W+/+eOvOj3/ywx+9+g9v3Hrnp6+/c2/34PBwWpGjnE4nlu3+/M07AG4c3u+YLEbT6ZSc6YCNcWxMJhNc1HUNmfIsLcuyaRpcVlVdNx0Gfd9P5k8vJprQY8a5KCrPj4I488Mkz8vJpMfzx5mcOMj9jekMXAZR4kZZkldlWTdtMz0Evp90TVVBEHIm95uOXDbtTGw2t+PFg5E6GGuy5iiao5qm4etB6LmGPBgrqu5Kij0cq5rhqrqjm95CZwL+YHv86k/efe3We1tv3f3prfd+dvv91279z9bt/7595+7tO7/aeuu9rdt3b99+7607v7r99n/9+qPhgjP5SZJElqQwDCbTSY//pj05H05SeDZLIf2E2IgoQnSZ9DOd2Rxd1+Z53rbtilk63Gra42abnmKwsnLcME5zcGBGnhwz9ewO5T6/eYwzbsKUZV13MG+JqcqWeDupU77a26uStKonTdPjGeBXwFN20bV1nKZp6MZObDtRkuihO7S2dwJNNf0wCuO2bJb8vJALP2kSjTlOEgVds1TVMRw1KYusKCM3dr28yBED1Zlg2Hw4HB4MBp7vtXWpi3tR5NZ5kgVFFDY1XRlngqMoGtPD9308aoofaoO7ljRKwzIM6wuBBwcHju3gWWks8geSKipJkIchVkpxDng0GnEcF4BzUYkHqq7ZoevPwNV9wXEcM7GDMMTC1MZD4+AgsPUsqIjY9XliDwaD/f1913URWE6gh57v20bkJQCfzxkya5qGARZgVAZFWySOrYlGFLd1XZ6vs+M4iJSyrMLcS8u0iEN1X/T97Hyx4WdBEIIgwJIPcz8t0rrMI8tzTLO6kKsGA6pzFxV+VqZ1kWVBqooqAh5GO0dsHOAMgy3AaZAJQ6lIi7qqp4fTc/yMCAM4LHzoXOdZFlU6L3u6TLLX9Aww8gbP8zBYlqYV1kPuJ3lc4iIqbNUzVTXLilPEZhdYwlEUEk6R73uOYgmeZ4SuaiHzmW4QamkSnqYz/cFa9/0gTtIgjk073tk/0MKhefCm8OEbvPC+9evvhc5w/vj0KAGmeaVage3HZdM3k8O6m7Q9e2RyOO0PkZQw6PvDo2S2lIaQEvsCmb3Iujxuyq7KyqYrqrzOixppCf+nRdW23dmuSlJNk6vQN5RAM3zHUN3QdRzfMG1T0TXPReSdCc6SGNbO8jzNszBI1L1RqEtVbMdW6Ft+WyZ13U7PchVdkiMkowJHWVqW5BtO6vmBFoY+bmBV1fdbVaIoYiUDi2SQ1GHeZGUSB3pgq34exyimZ4IRunEcAQk/B75rOLLnmRG465ZheGFko4afGiTkQG0JwwhBEqeR7cT3dvfUYGDsv859sDXm7xr/+0+BfXA8qGblJifFPQ7jjLYBrO5P+67rJk3flh1c11WTuphMTnEV/D/pOtJAkLpBQH3foRpOuhajdoIxIuRkZ8BGeA4ryTQtF1nXtnVdN00TZ1GABUUkJgWHLKuKAkLcL3UGURDu6fxOII1iWcxlLuLEVBx6Qz7ihYQQ7nAhR84JZ+XWEthznG2D207VDwKZrzUh45RGGSVjpVONqWEcGnIjE6pltVfd1l0C+667Z3K7mbodyXyl8hmH53g6hd7r+gSBJs2okZzGWeHsEs6Jci+UpVYbJ2OhELiUU1qVgcVSBEmlxOecXdtLYNd2dg1uJ9N2YkVqND7lxFLiUh6cjYkByQm4ICSUwgmwQ8EpEVuoVSHnISHhPBebshXlUhZr0a6sVc7bOsRW7xEwxB6JlXRcZ4DlSsaMYgXwCc57BreXqTuRIlQaYGqnQkO1UZnYGEu1DDzur4qNwKAGUz8M5FGhculYaRXYDJzVXtN6jRpMAsmdvCo2wDvGeDdTIDZXKXw2A8stWClKK4ulIFUiSAG4XtXZ3kGExTo6H77Q+VhQah2hplS6WutsLOaKVChKrVmls6KztT2w73H1R6MSk4/HiUQYQdFKVVtV6wSRjCW54vnSNIsVsL03cPb5dndcCVLLc5kiNwQvljCxLNWwMglOqVLkzraqRd4+xnnc3BuVgtJy45iGYgkpDWpuYmgZnInRLatcAjuOtTNwdrl2l4NNWqRQ8KHglnhZn7K5IAgvlJZZLlvbsQHeGTfb4Cy3Ap8pSoNoZGAwn6lA+DencN4dOHt8+9Gw2OcrbpwCLAg0muElOE0oiLu1XlE784TONsA7XPPRqBgINRW7YayItdUOnDEdSJZby6pWgsTaPiAG2+MqiA1X8XzONKeAmi1IOh0T+zRXAczLDZINMw8cRjm3zMnUbLVlVyeDxNnmoHMlyj10RsoQRSwFGIzkIZJBCNWCAFfVJ60t7fLu9tgSFY/nVFmxJMlUFMcwfMMIZMmSZZCNs20HJ8Qecgeitssriq5LIq+qiqrKuq7atgnCGHc0DZlXwvpdXVW7Q2FfMHY57MtMgecJVJaRsW16qCqdjR7o0k4kgxEP8B6vKZohigJ9GkLoFj3o1Qztud4JzgN+l9d3eU3VDXBW55wZmFYMhdWNVc4O0Vk4ECln3QRn+qC6AFOpFTbjKmcSYUMOnLfH6kjSufFYEERJkhaiAkO5kvMJnV1nwIljxR5IpqASPuCmaxpsgcOmpc+gB+6jM52B5yW2a5q2bjvs1tuOFFk0dFGcxUmOzTOptceOfl5uj7aDbC+8OFCrw6QI4gIPn7lzZ0N0aVleznab99nsnwpGjwcw2gAID2K7zekFwWTHVVSQsGk7ENrE87gvgRv0NDAV4Uz22+uAsRFKs4JsoasaY9h8DZ0hNuXc11TnpmnXNljHDEbap8kaYEibz8FnOPY+YtcNs3ZLTY0Z5u8GLsaZGqytyJsc7Pua8yRfBZcNiW0cLRH94uACrqI6M9x6OsO3M2sT5u0F8Ct+zmmEXSg2T9cZO2yi83p+hnkhdkPe2zUU36wVYUc6XyxOlsMTfu7m6xno9SIsX6xnAu7XNxiNMIy79cEFA4PWBTdzg7H1vE4mwZJK04Lp3JO0vfqW9QIRRqx1kfA6GSRZie0gArufTNYDI0iSlFSWkrwfbteLbSY22fpRP0P4NTIJizC2quYvOtfx86LcdCSBTdcLTwommYCsqrZbL5McjzASZBfPYRUTu52tqgusjeUIS+iSZEkb8q9T6BYGo7Hd0W37ffEnwTMnEyLv7++XQ5f9TCOsXbz+x/i+aXCluOcVS0MXy/vLnKMkL2mVJq/Gy3pG5HVUXVHPs5RO3LDSSuG5OM3powRZUGLIgrxzbBgMMlFsv/Q1Ic0Lz48RJ6RxS/MkLeLkaJBmOSSaE/kWssQZt7wwxtNhnOJMGVYLOoYkdPwLCvkBQ9VwHTe03cB2AnJ2Q8sJ6J0FBY4XmnaA2ZfBUcqJuqiYIEE2QLwE0nEWZJPHpawLZGxwkh5EKTP3UZAsMgkyKcu+9AURSYYtfV1E+9l+7vylJYkIm5VYRgxzzieno4WRFi2t7DUtsfTN1vSM48R6hokP1ziOvzTNCtgzCBM/TGAPkB/EuIQXQnoJF4bkTC6L+aeQBWeEZ0bcOw9PRDvCqu/nZqPELheJdWU9d3Q9d+vm7Wbeh7VrV8lFZ8AWQLdWQ1PQ7HmsM/h/tRXtfNGu1xlk8z4MCaheq1mftRUswmpC67TMiz6smSXQdbpeGtuLdbNm+1jXLHv289WwpsGOqiQx2HlV9rjYJBkUbI+wEsAXszb57DxZb0meDM9m3c5g0QG2izS0TnFnfRjdv9NydZ7JT2tc63lbUdH9wvoLg6YEwnxyseKOjSTSFQQmHSBlC2+3NPsSavujMWkapqs6w88IUsxCi1MJE0AR+I+eZ5TRy8WCPfoUEUYZScAAFxX404Bp2JnVECoOOS8i/xSd2ZKafzw/PPqGvpz1j97QIC1LimXYvmF5uknJ8jTTxZkMDBd32FnRbFbojpI+UvmQV3nRQK2k9dFATRwLGidq7BIVcyyQQjniNRTaJTDKP8onZpVB6pxmYwv3CekgBwKiqiyJHUTJiFfxB1KEJRPEz8q1Rcu1SQu1Icom+DtetCx2nOIJTC+plkzOhKGqOwspiFCqhftggO5lVWdMiYkxxbw/YISewJiNJVOkIrgrnKnOJmHLVCXkEBEUa8ZWs9hfoYgXxKs6w7ZMN56pjbkUi4ggzRoVsAXBHa4frYo919me8afKMz0XLsADmMgPlq0NMAediW2p2jiLBrU8Y8juU7OLJ6wdwdqSweSkelpsDGJ64lKSiSyYyw9OWBvhxTAQW5ydiQkXM7KJwMM96Sp+xnkGZmwlhXludoeKbawaDJmAgGkkMrGJtMTnFrtc3Ifm3qrYMRFbouqJ1EkLaYX5FMx58NyqzjAYmkqR/o2fWXhG7JJGmIEH4JSjCFtkzyDK6FvSDLmmpN09khEjlpjYAH89apnZD7KHh5WWzFa543rDsUD/oVRHmnvSkHWkyWpqXK5uE9i/nWJvXaEI/Q7eT88+GOr/AF0yOUoIWAUFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 24 13 48 44&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-fee1c.png&quot; data-srcset=&quot;/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-a67b7.png 200w,
/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-0b187.png 400w,
/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-fee1c.png 800w,
/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-b1a91.png 1200w,
/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-95179.png 1600w,
/static/2025-10-24-13-48-44-1e09e8d330005ce8316631032d341f55-f7915.png 1842w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.webpagetest.org/result/250807_ZiDc4K_4PX/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://www.webpagetest.org/result/250807&lt;em&gt;ZiDc4K&lt;/em&gt;4PX/&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1. 安装过程：
&lt;a href=&quot;https://github.com/catchpoint/WebPageTest.agent/blob/master/docs/docker.md&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://github.com/catchpoint/WebPageTest.agent/blob/master/docs/docker.md&lt;/a&gt;
 
&lt;br&gt;
 2. 说明文档：
&lt;a href=&quot;https://docs.webpagetest.org/getting-started/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://docs.webpagetest.org/getting-started/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/sitespeedio/sitespeed.io&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;sitespeed.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4.9K&lt;/td&gt;
&lt;td&gt;2025-08-05&lt;/td&gt;
&lt;td&gt;1. 支持 Firefox、Chrome、Android 版 Chrome，并对 OS X 和 iOS 版 Safari 提供有限支持
&lt;br&gt;
2. 支持本地在线测试（Docker 本地私有化部署）
&lt;br&gt;
3. 支持各种
&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/plugins/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;插件&lt;/a&gt;
，包含 Lighthouse/WebPageTest/PageSpeed Insights API
&lt;br&gt;
4. 支持
&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/continuously-run-your-tests/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;持续集成&lt;/a&gt;
&lt;br&gt;
5. 支持模拟慢速网络连接
&lt;br&gt;
6. 支持
&lt;a href=&quot;https://compare.sitespeed.io/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;页面性能/har 比较&lt;/a&gt;
&lt;br&gt;
7. 支持
&lt;a href=&quot;https://dashboard.sitespeed.io/d/9NDMzFfMk/page-metrics-desktop&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;线上性能监控&lt;/a&gt;
&lt;br&gt;
8. 
&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/metrics/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;指标全面&lt;/a&gt;
&lt;br&gt;
9. 支持
&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/scripting/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;脚本&lt;/a&gt;
&lt;br&gt;
10. 支持
&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/video/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;视频比较&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;想完全用起来还需要编写登录脚本或者让网站支持自动登录&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-6e10a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 308.8%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAA+CAIAAACk3KwKAAAACXBIWXMAAAsSAAALEgHS3X78AAAGbklEQVRIx41X247juBGd707+Yz8gr/mEAAEWi2wyu8F0T9DTtiXZ8k33O++kJHsPRdvTcrtnQ6htia3iKZ46VSx/qtSpMWcvrVNman3GI66wVX4lglouc7YqedjqUo3uKsSQceNe+3Q6nTGqqhJcnN8Nzihn7O2MMaauKnf/yX11XTcMg7uvuS6oyjqxL5pD2REhmR4wk1OJx0PRFd1lOSBb6DRNq+t6w3gaT2cuZbjbbff7tu3cZD+Mx2MUbndJms6Mi6Ko63rmntZ5nsdx3DSNm8GbAMjyLH1vLMRsz5jf7/ee57dt6x7da57nJUkyM8aSVV27++9scR5FMSHkZgy2sizDEjPC4FsURWxOLAbcoXQ22fd9M/nyHRnrBUHw1rgfBm16sHULDN5USnMhs9ue3ZeexhxTNYRVTdc27RVzqFtKKM/zcmYspbzF+TKjNOOSdLS7Gg/DCEsgl8Xc+I4qDKVNXrVZUbs423fGsazbvOrukbuOvJWhW2o8nWpuVD++XZQoQ1R/I8x+lWUZHmIxnuUAgPH8/40Lcl3VXw/5//bZt12Rx3lYkKQlDWXG9NjRaMdpujkN0+0sVIiz7612m4B0zVmPP73+8x/hopeGcwUuBeWEKKk1QsCZwvzMGMJerbxgvYYABjO8NuvP2wXjQjDJQEZH21YgVRiTlAgt9L22fd/fbDZpkiqjz+b81fOOddHLXkjBKe+6CzLszR1ynuVQ/GScSAVX1esujOtScf3nyMg+3/Og0CSONVgy/a4uouaCbPf8A2SrbT9YB+vD/qC07rXZV8U6TzSHmDknrGulUIpzSakwYm6cZhnchv1hv4fbSICorl6SUFI1d1sQwu/dBk/eyroNY3AskDp189txxTtOKCFNV9cohQgYa1umuLp3Gz6DsDiKkPHwnHL5OV4xlD8l+RRndYuzerdnhGq9Xh8PR7htwZn65fgalxWopx2B22Jy+wHbrhgA+Xg8gm1wM8jxX/Fike5REzhlN4Uxpu7ZRkEMgAy2DweHbET/Wuy+HHysRDvaWGTBH8Z52nOwWa9RaxFlS7gwOWt/P3hayklhEI56jOz2DLeBPBEDA9UJ/sv+G6on3G4bG6rHyPm05zAMsWfLNgodt/T+fHxJmkIzaZE/Uhj2bLW93jiRAJlQ0ev+P9HSz/aaCossPtJ2lgN5G26jiW1lq59CbvpF5Od745DVB/mME2S1WmHb4Sa0WczBED5EUpcv8Ya31CqMQSq8e68wGPueb8G3W4gBZq7KVm3323EB46bhqA3Uphd/IE/sGQqLoxg8wUVsD8rEEr9HS9YQuD0tKhl9lJLO2OUzjPGezUEmnxMPFQ4KEwoqUQ+MwbaLMwizyMoiM9Q+rpfZtigLFAPh4ox8lu8qyXqKM1axyDbvsTWIqY/rvChLSvRVYY9qWDBRDXne9syE1NxELY6Xgjpk/gg5uyoscvk8KYxLhfSISZX9GDlLM4QqDLeI2ZUwa4zqWbIOvQCQbVV4zPZkDLZxcN/c5hNhLafbJEWosJa4uD03xkHn3Eb7cmGbO2PFBN/myaGoDk2JSgLk/n2cF4sFQt02zUVhSER0NARSZbwjOJaTtjiUeVbW8qawW2eAJhCpa9B/2T8tpIHCcDqLqRi0LbyBR8aW3lsB/OjsRVaiCRn6wfQ91i2KCqv2CIRU/XTuztoKN942UeiaBvwZQ6xxgRBiQqPfMOZPjNHfvG0R8HT77+nucEcDuN3u0jS7c34ECninyOMGutGMkaYEJ/d7fruwux+HoVguNsunl5dfl98+b7zn9fJp9e0zYuDe+872zcnvxugUw9ALvgT+cxA8+/5T4D+tg69Q2gwZfICah8aw8T1rdrvkxfiKjK7zI+TJ+MvMWLCZsW2TQKgxMMA1TDeDlFUYLL3/+qsvsL9d98iIJq8r0baaUlyKEHx2Wb7ZrNIqxvEoBLFaFbjorUu9NnGkror8rnedOsgfdoBue7vtDsfF4AJ2OkGWSJUkSSFQVKhkapFQZDhnVVkWeTHvANt2u9k8h0kp+itnI9IX28ED6AQhqKGUUvdLYeY2+uIsTdZRnjCrHqzXn874OcXNYE5nFwjEEievk/2HWeWU9veA/PXf+V9+zf72WrvlPux6b4MKXTEwzTdR+rQrvu7zRYxzSuFnHr0WkA+NT1fkpqkL1OskbdJstCSe3/0YOP8Bi2/iG4yHsNMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 24 13 52 23&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-fee1c.png&quot; data-srcset=&quot;/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-a67b7.png 200w,
/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-0b187.png 400w,
/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-fee1c.png 800w,
/static/2025-10-24-13-52-23-fc758e87b31fc43a03bcd6ee5374f6f6-6e10a.png 1000w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.sitespeed.io/documentation/sitespeed.io/web-performance-testing-in-practice/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;最佳实践&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;线上监控&quot;&gt;&lt;a href=&quot;#%E7%BA%BF%E4%B8%8A%E7%9B%91%E6%8E%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;线上监控&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选型&lt;/th&gt;
&lt;th&gt;Star 数&lt;/th&gt;
&lt;th&gt;最后代码提交时间&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sentry&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;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;web-vitals&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;8.1K&lt;/td&gt;
&lt;td&gt;2025-08-01&lt;/td&gt;
&lt;td&gt;健康网站基本指标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Lighthouse&lt;/a&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;a href=&quot;https://github.com/Zizzamia/perfume.js&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Perfume.js&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3.2K&lt;/td&gt;
&lt;td&gt;2024-06-04&lt;/td&gt;
&lt;td&gt;用于测量所有性能重要指标的 Web 性能库，web-vitals 的超集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/akamai/boomerang&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;boomerang&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;面向最终用户的 Web 性能测试和指标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/get-started&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PageSpeed Insights API&lt;/a&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;a href=&quot;https://github.com/sitespeedio/sitespeed.io&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;sitespeed.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4.9K&lt;/td&gt;
&lt;td&gt;2025-08-05&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTelemetry&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;Elastic APM&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;Jaeger&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;Pinpoint&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;Prometheus + Grafana&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最终选定 sitespeed.io 作为性能测试工具，暂不考虑线上性能监控&lt;/p&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;p&gt;这里只量化首次进入页面的性能&lt;/p&gt;
&lt;h2 id=&quot;目录结构&quot;&gt;&lt;a href=&quot;#%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;目录结构&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32255424788186130000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`└── test
    └── perf
        ├── .env.example
        ├── .gitignore
        ├── config.json
        ├── index.mjs
        ├── loadEnv.js
        └── start.sh`, `32255424788186130000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;└── test
    └── perf
        ├── .env.example
        ├── .gitignore
        ├── config.json
        ├── index.mjs
        ├── loadEnv.js
        └── start.sh&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;核心代码&quot;&gt;&lt;a href=&quot;#%E6%A0%B8%E5%BF%83%E4%BB%A3%E7%A0%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;核心代码&lt;/h2&gt;
&lt;p&gt;.env.example&lt;/p&gt;
&lt;p&gt;配置样例代码，复制一份为 env，在改为自己的账号信息&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74627676459123430000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`ACCOUNT=xxx
TOKEN=xxx`, `74627676459123430000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ACCOUNT=xxx
TOKEN=xxx&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;.gitignore&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;81351168077378130000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`output
.env`, `81351168077378130000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;output
.env&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;config.json&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;73482366618526290000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
   &amp;quot;browsertime&amp;quot;: {
      &amp;quot;iterations&amp;quot;: 1,
      &amp;quot;urls&amp;quot;: [
         {
            &amp;quot;url&amp;quot;: &amp;quot;网页链接1&amp;quot;,
            &amp;quot;label&amp;quot;: &amp;quot;网页名称1&amp;quot;
         },
         {
            &amp;quot;url&amp;quot;: &amp;quot;网页链接2&amp;quot;,
            &amp;quot;label&amp;quot;: &amp;quot;网页名称2&amp;quot;
         }
      ]
   },
   &amp;quot;multi&amp;quot;: true,
   &amp;quot;cpu&amp;quot;: true
}`, `73482366618526290000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;json&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;json&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;browsertime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;iterations&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;urls&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;网页链接1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;网页名称1&quot;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;网页链接2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;网页名称2&quot;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;multi&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;cpu&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;index.mjs&lt;/p&gt;
&lt;p&gt;主要实现自动登录功能&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;8614705028942038000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import loadEnv from &apos;./loadEnv.js&apos;;

await loadEnv();

const { ACCOUNT, TOKEN } = process.env;

export default async function(context, commands) {
   const urls = context.options.urls;
   if (!urls || urls.length === 0) {
      throw new Error(&apos;No URLs provided to test&apos;);
   }

   for (let item of urls) {
      const [base, hash] = item.url.split(&apos;#&apos;); // 分离基础URL和哈希部分
      const [hashPath, hashQuery] = hash.split(&apos;?&apos;); // 分离哈希路径和参数
      const params = new URLSearchParams(hashQuery);
      // 用于登录的参数通过 url 传递，业务端需要实现对应功能
      params.append(&apos;username&apos;, ACCOUNT); // 添加新参数
      params.append(&apos;token&apos;, TOKEN); // 添加新参数
      const newUrl = \`\${base}#\${hashPath}?\${params.toString()}\`;

      await commands.measure.start(newUrl, item.label);
      // When the test is finished, clear the browser cache
      await commands.cache.clear();
      // Navigate to a blank page so you kind of start from scratch for the next URL
      await commands.navigate(&apos;about:blank&apos;);
   }
}`, `8614705028942038000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadEnv &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./loadEnv.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadEnv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ACCOUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TOKEN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urls &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;urls &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;No URLs provided to test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;base&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hash&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 分离基础URL和哈希部分&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;hashPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hashQuery&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hash&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;?&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 分离哈希路径和参数&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hashQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 用于登录的参数通过 url 传递，业务端需要实现对应功能&lt;/span&gt;
      params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ACCOUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 添加新参数&lt;/span&gt;
      params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;token&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 添加新参数&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;base&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;hashPath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;measure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// When the test is finished, clear the browser cache&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Navigate to a blank page so you kind of start from scratch for the next URL&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;about:blank&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;loadEnv.js&lt;/p&gt;
&lt;p&gt;实现读取 env 配置的功能&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30242919416095228000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import fs from &apos;fs/promises&apos;;
import path from &apos;path&apos;;
import { fileURLToPath } from &apos;url&apos;;

// 解决ES模块中__dirname缺失问题
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
 * 解析.env文件并设置环境变量
 * @param {string} [envPath=&apos;.env&apos;] - 自定义.env文件路径
 */
async function loadEnv(envPath = &apos;.env&apos;) {
   try {
      // 构建.env文件绝对路径
      const resolvedPath = path.resolve(__dirname, envPath);

      // 读取文件内容
      const content = await fs.readFile(resolvedPath, &apos;utf8&apos;);

      // 解析每一行并设置环境变量
      content.split(&apos;\n&apos;).forEach((line) =&gt; {
         // 忽略注释和空行
         const trimmedLine = line.trim();
         if (!trimmedLine || trimmedLine.startsWith(&apos;#&apos;)) return;

         // 提取键值对（支持等号前后有空格的情况）
         const [key, value] = trimmedLine.split(&apos;=&apos;).map((item) =&gt; item.trim());
         if (key &amp;&amp; value) {
            process.env[key] = value; // 设置环境变量
         }
      });

      // console.log(\`✅ .env loaded from \${resolvedPath}\`);
   } catch (error) {
      // console.warn(\`⚠️ No .env file found at \${envPath}, using system environment variables\`);
   }
}

// 导出默认函数
export default loadEnv;`, `30242919416095228000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;fs/promises&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; path &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;path&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fileURLToPath &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 解决ES模块中__dirname缺失问题&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; __filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fileURLToPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; __dirname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * 解析.env文件并设置环境变量
 * @param {string} [envPath=&apos;.env&apos;] - 自定义.env文件路径
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadEnv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;envPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;.env&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 构建.env文件绝对路径&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resolvedPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; envPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 读取文件内容&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolvedPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 解析每一行并设置环境变量&lt;/span&gt;
      content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 忽略注释和空行&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; trimmedLine &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;trimmedLine &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; trimmedLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// 提取键值对（支持等号前后有空格的情况）&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; trimmedLine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;=&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 设置环境变量&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// console.log(`✅ .env loaded from ${resolvedPath}`);&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// console.warn(`⚠️ No .env file found at ${envPath}, using system environment variables`);&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 导出默认函数&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; loadEnv&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;start.sh&lt;/p&gt;
&lt;p&gt;各终端性能测试运行策略，这里除了测试性能，还测试了各个终端浏览器缩放比例的显示效果，以下为需要测试的终端&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;屏幕类型&lt;/th&gt;
&lt;th&gt;物理分辨率&lt;/th&gt;
&lt;th&gt;推荐缩放比例&lt;/th&gt;
&lt;th&gt;适用场景与说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24 英寸及以下显示器&lt;/td&gt;
&lt;td&gt;1920×1080&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;日常办公与网页浏览，PPI 约 93-102，文字清晰不累眼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.6 英寸笔记本普通屏&lt;/td&gt;
&lt;td&gt;1366
*
768&lt;/td&gt;
&lt;td&gt;Windows 默认缩放 125%，macOS（如 MacBook 外接）推荐 125%-150%&lt;/td&gt;
&lt;td&gt;文字清晰且兼顾性能，适合文档处理、网页浏览和视频播放&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13 英寸 MacBook Pro&lt;/td&gt;
&lt;td&gt;2560×1600（等效逻辑分辨率 1440×900）&lt;/td&gt;
&lt;td&gt;200%（Retina）&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30350601988566163000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`current_time=\$(date +&amp;quot;%Y-%m-%d-%H-%M-%S&amp;quot;)

# 台式机桌面端 1920x1080 运行 5 次
docker run -e MAX_OLD_SPACE_SIZE=4096 -e TZ=Asia/Shanghai --rm -v \$(pwd):/sitespeed.io sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort 1920x1080 --outputFolder ./output/\$current_time/desktop -n 5

# 苹果笔记本端 1440*900 运行 1 次
docker run -e MAX_OLD_SPACE_SIZE=4096 -e TZ=Asia/Shanghai --rm -v &amp;quot;\$(pwd):/sitespeed.io&amp;quot; sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort 1440*900 --outputFolder ./output/\$current_time/mac

# windows/ubuntu 笔记本端 1366x768 运行 1 次
docker run -e MAX_OLD_SPACE_SIZE=4096 -e TZ=Asia/Shanghai --rm -v &amp;quot;\$(pwd):/sitespeed.io&amp;quot; sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort 1366x768 --outputFolder ./output/\$current_time/laptop`, `30350601988566163000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;current_time&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;date&lt;/span&gt; +&lt;span class=&quot;token string&quot;&gt;&quot;%Y-%m-%d-%H-%M-%S&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 台式机桌面端 1920x1080 运行 5 次&lt;/span&gt;
docker run -e &lt;span class=&quot;token assign-left variable&quot;&gt;MAX_OLD_SPACE_SIZE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt; -e &lt;span class=&quot;token assign-left variable&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Asia/Shanghai --rm -v &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;:/sitespeed.io sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort 1920x1080 --outputFolder ./output/&lt;span class=&quot;token variable&quot;&gt;$current_time&lt;/span&gt;/desktop -n &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 苹果笔记本端 1440*900 运行 1 次&lt;/span&gt;
docker run -e &lt;span class=&quot;token assign-left variable&quot;&gt;MAX_OLD_SPACE_SIZE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt; -e &lt;span class=&quot;token assign-left variable&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Asia/Shanghai --rm -v &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;:/sitespeed.io&quot;&lt;/span&gt; sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort &lt;span class=&quot;token number&quot;&gt;1440&lt;/span&gt;*900 --outputFolder ./output/&lt;span class=&quot;token variable&quot;&gt;$current_time&lt;/span&gt;/mac

&lt;span class=&quot;token comment&quot;&gt;# windows/ubuntu 笔记本端 1366x768 运行 1 次&lt;/span&gt;
docker run -e &lt;span class=&quot;token assign-left variable&quot;&gt;MAX_OLD_SPACE_SIZE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt; -e &lt;span class=&quot;token assign-left variable&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Asia/Shanghai --rm -v &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;:/sitespeed.io&quot;&lt;/span&gt; sitespeedio/sitespeed.io:38.1.1 --config config.json index.mjs --browsertime.viewPort 1366x768 --outputFolder ./output/&lt;span class=&quot;token variable&quot;&gt;$current_time&lt;/span&gt;/laptop&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;运行步骤&quot;&gt;&lt;a href=&quot;#%E8%BF%90%E8%A1%8C%E6%AD%A5%E9%AA%A4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;运行步骤&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;55813862567832830&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`cp ./.env.example ./.env &amp;&amp; vim ./.env # 在线上环境找到对应的 token，设置自己的环境变量 ACCOUNT/TOKEN，否则不能正常测试

cd ./test/perf &amp;&amp; chmod +x ./start.sh &amp;&amp; ./start.sh`, `55813862567832830`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt; ./.env.example ./.env &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; ./.env &lt;span class=&quot;token comment&quot;&gt;# 在线上环境找到对应的 token，设置自己的环境变量 ACCOUNT/TOKEN，否则不能正常测试&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ./test/perf &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x ./start.sh &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./start.sh&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;对比维度&quot;&gt;&lt;a href=&quot;#%E5%AF%B9%E6%AF%94%E7%BB%B4%E5%BA%A6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;对比维度&lt;/h1&gt;
&lt;p&gt;选择以下维度进行对比&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;页面传输资源大小（KB）&lt;/li&gt;
&lt;li&gt;本页面相关 xhr 请求数量&lt;/li&gt;
&lt;li&gt;85% 页面可见耗时 5 次耗时中位数（s）&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;其他&quot;&gt;&lt;a href=&quot;#%E5%85%B6%E4%BB%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;其他&lt;/h1&gt;
&lt;p&gt;在性能测试的过程中，发现 nginx 没有开启 gzip，需要配置一下 nginx&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92676970330910540000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`gzip on;
gzip_comp_level 6;         # 压缩等级，1-9，推荐 5~6，数值越大压缩率越高但 CPU 占用更高
gzip_min_length 1k;        # 只压缩大于 1k 的响应体
gzip_buffers 16 8k;        # 压缩缓冲区设置
gzip_http_version 1.1;     # 默认即可，http/1.1及以上
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; # 需要压缩的内容类型
gzip_vary on;              # 响应头增加 Vary: Accept-Encoding，便于代理缓存
gzip_proxied any;          # 允许代理服务器压缩
gzip_disable &amp;quot;msie6&amp;quot;;      # 禁用 IE6 gzip

# 可选: 避免对图片、压缩包等二进制文件压缩
# gzip_types 省略常见的二进制类型，如 image/*, application/zip, application/gzip`, `92676970330910540000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;nginx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;nginx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;nginx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-nginx line-numbers&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;gzip&lt;/span&gt; on&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_comp_level&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;# 压缩等级，1-9，推荐 5~6，数值越大压缩率越高但 CPU 占用更高&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_min_length&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;# 只压缩大于 1k 的响应体&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_buffers&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;# 压缩缓冲区设置&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_http_version&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;     &lt;span class=&quot;token comment&quot;&gt;# 默认即可，http/1.1及以上&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_types&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;plain text&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;css application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;json application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;javascript text&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;xml application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;xml application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;xml&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;rss text&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;javascript image&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;xml&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 需要压缩的内容类型&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_vary&lt;/span&gt; on&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;              &lt;span class=&quot;token comment&quot;&gt;# 响应头增加 Vary: Accept-Encoding，便于代理缓存&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_proxied&lt;/span&gt; any&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;          &lt;span class=&quot;token comment&quot;&gt;# 允许代理服务器压缩&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;gzip_disable&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;msie6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;# 禁用 IE6 gzip&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 可选: 避免对图片、压缩包等二进制文件压缩&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# gzip_types 省略常见的二进制类型，如 image/*, application/zip, application/gzip&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[数据库连接工具 web 版]]></title><description><![CDATA[介绍 dbgate 支持多种数据库连接，详见  https://github.com/dbgate/dbgate?tab=readme-ov-file Dockerfile 以下是开发过程中用到的连接格式 启动 打开  http://localhost:3010]]></description><link>https://blog.towavephone.com/database-connection-tool-web-based/</link><guid isPermaLink="false">https://blog.towavephone.com/database-connection-tool-web-based/</guid><pubDate>Thu, 23 Oct 2025 16:40:42 GMT</pubDate><content:encoded>&lt;h1 id=&quot;介绍&quot;&gt;&lt;a href=&quot;#%E4%BB%8B%E7%BB%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;介绍&lt;/h1&gt;
&lt;p&gt;dbgate 支持多种数据库连接，详见 &lt;a href=&quot;https://github.com/dbgate/dbgate?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://github.com/dbgate/dbgate?tab=readme-ov-file&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;dockerfile&quot;&gt;&lt;a href=&quot;#dockerfile&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dockerfile&lt;/h1&gt;
&lt;p&gt;以下是开发过程中用到的连接格式&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;79217452949890510000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`version: &apos;3&apos;
services:
   dbgate:
      image: dbgate/dbgate
      restart: always
      # network_mode: host
      extra_hosts:
         - &apos;host.docker.internal:host-gateway&apos;
      ports:
         - 3010:3000
      volumes:
         - dbgate-data:/root/.dbgate
      environment:
         # LOGIN: user1,user2

         # 这里可以配置登录用户的账号密码，不配置的默认不要密码
         # LOGIN_PASSWORD_user1: xxxx
         # LOGIN_PASSWORD_user2: xxxx

         # 这里配置哪些用户能看到哪些数据库连接
         # LOGIN_PERMISSIONS_user1: ~connections/*,connections/con1,connections/con2
         # LOGIN_PERMISSIONS_user2: ~connections/*,connections/con3

         CONNECTIONS: con1,con2,con3

         LABEL_con1: 正式服
         SERVER_con1: xxxx
         USER_con1: xxxx
         PASSWORD_con1: xxxx
         PORT_con1: 5432
         ENGINE_con1: postgres@dbgate-plugin-postgres

         LABEL_con2: 正式服
         # 格式为：mongodb://用户名:密码@地址:端口/数据库名
         # 密码里面遇到 # 需要转义成 %23
         # 遇到不是分片的 mongo 数据库时候需要加上 ?directConnection=true 查询字符串
         URL_con2: xxxx
         ENGINE_con2: mongo@dbgate-plugin-mongo

         LABEL_con3: 本地（读写）
         SERVER_con3: host.docker.internal
         PORT_con3: 6379
         ENGINE_con3: redis@dbgate-plugin-redis

volumes:
   dbgate-data:
      driver: local`, `79217452949890510000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;yml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;yml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-yml line-numbers&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;3&apos;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;dbgate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dbgate/dbgate
      &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always
      &lt;span class=&quot;token comment&quot;&gt;# network_mode: host&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;extra_hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;host.docker.internal:host-gateway&apos;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; 3010&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; dbgate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/root/.dbgate
      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# LOGIN: user1,user2&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;# 这里可以配置登录用户的账号密码，不配置的默认不要密码&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# LOGIN_PASSWORD_user1: xxxx&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# LOGIN_PASSWORD_user2: xxxx&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;# 这里配置哪些用户能看到哪些数据库连接&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# LOGIN_PERMISSIONS_user1: ~connections/*,connections/con1,connections/con2&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# LOGIN_PERMISSIONS_user2: ~connections/*,connections/con3&lt;/span&gt;

         &lt;span class=&quot;token key atrule&quot;&gt;CONNECTIONS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; con1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;con2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;con3

         &lt;span class=&quot;token key atrule&quot;&gt;LABEL_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 正式服
         &lt;span class=&quot;token key atrule&quot;&gt;SERVER_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; xxxx
         &lt;span class=&quot;token key atrule&quot;&gt;USER_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; xxxx
         &lt;span class=&quot;token key atrule&quot;&gt;PASSWORD_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; xxxx
         &lt;span class=&quot;token key atrule&quot;&gt;PORT_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5432&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;ENGINE_con1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres@dbgate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;plugin&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;postgres

         &lt;span class=&quot;token key atrule&quot;&gt;LABEL_con2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 正式服
         &lt;span class=&quot;token comment&quot;&gt;# 格式为：mongodb://用户名:密码@地址:端口/数据库名&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# 密码里面遇到 # 需要转义成 %23&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;# 遇到不是分片的 mongo 数据库时候需要加上 ?directConnection=true 查询字符串&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;URL_con2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; xxxx
         &lt;span class=&quot;token key atrule&quot;&gt;ENGINE_con2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mongo@dbgate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;plugin&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;mongo

         &lt;span class=&quot;token key atrule&quot;&gt;LABEL_con3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 本地（读写）
         &lt;span class=&quot;token key atrule&quot;&gt;SERVER_con3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; host.docker.internal
         &lt;span class=&quot;token key atrule&quot;&gt;PORT_con3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6379&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;ENGINE_con3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; redis@dbgate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;plugin&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;redis

&lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;dbgate-data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; local&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;启动&quot;&gt;&lt;a href=&quot;#%E5%90%AF%E5%8A%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;启动&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;63364989447721250000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`docker-compose -f ./dbgate.yml up -d`, `63364989447721250000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-compose -f ./dbgate.yml up -d&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;打开 &lt;a href=&quot;http://localhost:3010&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;http://localhost:3010&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[React 全局状态库选型]]></title><description><![CDATA[背景 目前老项目使用的 redux 性能较差且难上手/代码复杂，useContext 虽然使用上简单但性能较差，需要选型一个易上手/性能好/组件化的三方库，以下是 redux/useContext 在处理复杂业务时的缺点 组件间的状态共享只能通过将 state…]]></description><link>https://blog.towavephone.com/react-global-state-selection/</link><guid isPermaLink="false">https://blog.towavephone.com/react-global-state-selection/</guid><pubDate>Wed, 22 Oct 2025 18:34:06 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;目前老项目使用的 redux 性能较差且难上手/代码复杂，useContext 虽然使用上简单但性能较差，需要选型一个易上手/性能好/组件化的三方库，以下是 redux/useContext 在处理复杂业务时的缺点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组件间的状态共享只能通过将 state 提升至它们的公共祖先来实现，但这样做可能导致重新渲染一颗巨大的组件树（管理成本和子组件意外渲染问题）&lt;/li&gt;
&lt;li&gt;Context 只能存储单一值，无法存储多个各自拥有消费者的值的集合&lt;/li&gt;
&lt;li&gt;以上两种方式都很难将组件树的顶层与子组件进行代码分割&lt;/li&gt;
&lt;li&gt;useContext 也没有提供对异步请求的解决方案，redux 虽然有但较难上手&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;目标&quot;&gt;&lt;a href=&quot;#%E7%9B%AE%E6%A0%87&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;目标&lt;/h1&gt;
&lt;p&gt;提供一种机制来管理和维护 React 应用中的状态，并且使得这些状态能够跨组件共享、状态的变化可以预测&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;h1 id=&quot;模式&quot;&gt;&lt;a href=&quot;#%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;模式&lt;/h1&gt;
&lt;h2 id=&quot;store&quot;&gt;&lt;a href=&quot;#store&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Store&lt;/h2&gt;
&lt;p&gt;Store 模式通过集中式的存储来管理应用的状态，这种模式的基本思想是将应用的所有状态存储在一个单一的地方，通常称为“store”。组件通过与 store 的交互来读取和更新状态，而不是直接在组件内部管理状态&lt;/p&gt;
&lt;p&gt;Redux、Zustand 正是使用了 store 模式设计，Redux 官网的 gif 很好的演示了 redux 中数据流动的方向及 Store、 Dispach、Action、Reducer 的配合过程&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/2025-10-22-18-43-58-edcdc14f32727d2aa89fe335da19034b.gif&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;响应&quot;&gt;&lt;a href=&quot;#%E5%93%8D%E5%BA%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;响应&lt;/h2&gt;
&lt;p&gt;响应模式是一种通过观察（observe）状态变化并自动更新界面的方法。这种模式的核心思想是，当数据发生变化时，界面能够自动地响应并更新显示，可以使得状态管理更加直观、高效和模块化&lt;/p&gt;
&lt;p&gt;MobX、Valtio 都使用了响应模式，MobX 官网流程图很好的解释了其 data flow&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-a9fd5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 21.5625%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsSAAALEgHS3X78AAAApElEQVQI112NzQuCQBDF/ev7eyro2sGTYJEHLwUmJBTiB7qu7syuOzbrB0S/y7zHm8fzpJRN21bp9R2dqiRk2wlxuDx3+/AcZwOnTfN4BUF8zOtUCFnleRbd7r5fJIk3zWjEuviMxhA5a63tFFpLS2qMUTCM1q52HLXWLNbyRJMCVArUDB8EBQBlWfIygnMuGBysiEeItvIPtMGaX5eRPxCx7/svkVzkrDJb6KUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 10 47 41&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-fee1c.png&quot; data-srcset=&quot;/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-a67b7.png 200w,
/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-0b187.png 400w,
/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-fee1c.png 800w,
/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-b1a91.png 1200w,
/static/2025-10-23-10-47-41-ab316e215018c0c656602e5ce3a3f29b-a9fd5.png 1280w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;原子&quot;&gt;&lt;a href=&quot;#%E5%8E%9F%E5%AD%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;原子&lt;/h2&gt;
&lt;p&gt;不同于 Store 模式和响应模式把 State 集中起来管理，状态维护在组件顶部，子组件需要的话通过 selector 按需获取，数据自上向下流动。原子模式提倡把应用的全局状态拆分为多个小的、独立的状态单元——这些状态单元被称为原子（Atom），提供更细粒度的状态管理，以便组件可以更高效地更新和渲染&lt;/p&gt;
&lt;p&gt;原子模式是对 useState + useContext 的升级，原子模式的代表作是 Recoil 和 Jotai&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-a9fd5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.25000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABqklEQVQoz01SPZPTMBDNf6ajo6SCoaGm4KOAoYAGuoOBYYDhiM85X85OnPgs2Y5iR9+yJX+wtpm7e6NZP8na3bdvtIjjOAgC55rKf6bpXkSvzNGXyUeJv5riBw3fDMPQd+0wfnoI3YR+4ouiKBBCwHR9ImUiGZGs7DunRNV1zijmbANLsOOU3g33sLhl/dDrmtm+ef/80db7+evzu+8fXq5/f/n04slN6L99+pAS9Prxg+j6KoyiPMvGZBAAMpxzEE0jbWfj9Z/jISVJhEKPAtsEklMUXtzgw7ezs0107V2MkFKOnRljwLTWjFFS5nVjhRGMFlpQJSjMp7QpTgrlVYhOcLOqKkJIXddjctu2SinYLJfebh9LxeCwVqeCoEOWCM6LSlrXgkfBBiul72aefQPZnPMsy61tGqestTjZSsmzksS7bYZS8JVUPK8UF1Ir1U+4M2zuD0QqfnnpX/nLKE64aoQsA//varU691braAcSoL4x5r/bc5m5vxACRtrvE4yzNE2hSVmWQLIRGGNEKYU7eZ6DU4vbzP7eG5jJXA4UzX/ntwHRToDzf73vL2xIupl3AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 10 56 17&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-fee1c.png&quot; data-srcset=&quot;/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-a67b7.png 200w,
/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-0b187.png 400w,
/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-fee1c.png 800w,
/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-b1a91.png 1200w,
/static/2025-10-23-10-56-17-090276aed9e3109dd178671828683c3d-a9fd5.png 1280w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;状态机&quot;&gt;&lt;a href=&quot;#%E7%8A%B6%E6%80%81%E6%9C%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;状态机&lt;/h2&gt;
&lt;p&gt;状态机是一种数学模型，它描述了在任何给定时间只能处于一种状态的系统，系统在不同的状态之间可以通过确定的事件进行转化&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-10-56-28-215f7f0632ac27f4ecb13d83e21181c8-180cb.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 780px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 53.84615384615385%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAAB10lEQVQoz2WS22sTQRTG+4/5KPjkv2EpPlhqoGgUpUR8ExEU9UFp8R68gJKq8YKiqTam2WSz7U67t5nZ2ZnJXjKz2ZhERacJbZMKv4dhON+c7ztnZhBLx0AqvUA4pGPjyPGTq0V+4S5bWmHn79AvjRgFse0nqkCxL5nZV5KwF6V/dtCuUrG0HORu0sXbdP568L7KgzDdtBlLBrwzhHRCPFZqhvvo6erjZ68fFkuf15oy60ciVcis50BefFFevv/8wZNXbz9+w7x7IFZOuPhVKldm506fyRfmTuZu3LrXFkPMJWaSJ/3vG+aJ2YWz5y7NL+QLl6+pTojKA9uj/qmNY1W3A0OPyoYlNoCob4saEABKGHTqhgsc5pHO4cxj1JMWii3I15rsaA4eW0TH8/jIKVhYwYTFACUkHgTJAI7a/ieOB347UylcInRbIQ1HqoPld8E21H/UgN6qr9dI1IeHbCtaumnBCPIM85S09+C749w03dUrFxuf3hgtE7enM8ORZ6Na4Q6IxU9vz9gYlVMO/6baO/C17Ce/0ZRtKv2o39KavlGD6x9iFqitwonPQ8LMQZT4KEm6Hk2nB6bEYW8LuJXSy3pVM50AUTHZWS1W2/J0E1o4mrz/Bx30ODHeQb04AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 10 56 28&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-10-56-28-215f7f0632ac27f4ecb13d83e21181c8-180cb.png&quot; data-srcset=&quot;/static/2025-10-23-10-56-28-215f7f0632ac27f4ecb13d83e21181c8-b84dc.png 200w,
/static/2025-10-23-10-56-28-215f7f0632ac27f4ecb13d83e21181c8-8d9eb.png 400w,
/static/2025-10-23-10-56-28-215f7f0632ac27f4ecb13d83e21181c8-180cb.png 780w&quot; data-sizes=&quot;(max-width: 780px) 100vw, 780px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h1 id=&quot;选型&quot;&gt;&lt;a href=&quot;#%E9%80%89%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;选型&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状态库&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;响应粒度&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;API 简洁度&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;性能&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;类型支持&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;异步处理&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;持久化/中间件&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;DevTools&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;社区生态&lt;/th&gt;
&lt;th align=&quot;right&quot;&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;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context API&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;粗粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;简单&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;一般&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;无&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中&lt;/td&gt;
&lt;td&gt;全局共享、简单场景，易用&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
应用中全局状态较少、简单的场景；
&lt;/li&gt;
&lt;li&gt;
如主题切换、登录状态、国际化等
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&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;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
性能较差，任何 context 改变会导致所有消费组件重渲染；
&lt;/li&gt;
&lt;li&gt;
不适合管理复杂状态
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redux&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;粗粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;复杂&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;一般&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;强（Redux DevTools）&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;中/大&lt;/td&gt;
&lt;td&gt;可预测性、可扩展性、生态强&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
复杂的大型应用，业务逻辑复杂，状态交互频繁；
&lt;/li&gt;
&lt;li&gt;
需要时间旅行、调试工具、可预测性强的场景
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
社区成熟，生态丰富（如 Redux Toolkit）；
&lt;/li&gt;
&lt;li&gt;
可预测性强，状态集中管理；
&lt;/li&gt;
&lt;li&gt;
易于调试，工具支持好（Redux DevTools）；
&lt;/li&gt;
&lt;li&gt;
与 React 以外的框架也可结合使用
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
写法繁琐（action、reducer、store 等），模板代码多；
&lt;/li&gt;
&lt;li&gt;
学习曲线较高；
&lt;/li&gt;
&lt;li&gt;
对于简单场景显得“重”
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MobX&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;中等粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;简单&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;中/大&lt;/td&gt;
&lt;td&gt;响应式、面向对象、灵活&lt;/td&gt;
&lt;td&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;/td&gt;
&lt;td&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;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
状态分散，不易全局管控；
&lt;/li&gt;
&lt;li&gt;
调试和开发工具支持较 Redux 差；
&lt;/li&gt;
&lt;li&gt;
难以追踪复杂的状态变更
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recoil&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;原子粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;适中&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有（Recoil DevTools）&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;较成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;中/大&lt;/td&gt;
&lt;td&gt;原子+选择器、复杂依赖关系&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
中大型应用；
&lt;/li&gt;
&lt;li&gt;
需要细粒度状态管理和较高性能的场景
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
原生支持 React，API 简洁；
&lt;/li&gt;
&lt;li&gt;
支持原子化状态管理，细粒度控制；
&lt;/li&gt;
&lt;li&gt;
性能优秀，支持异步 selector
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
社区和生态相对较小；
&lt;/li&gt;
&lt;li&gt;
有些高级用法不如 Redux 成熟
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zustand&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;任意粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极简&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有（Zustand DevTools）&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中/大&lt;/td&gt;
&lt;td&gt;Hook 风格、中间件、极简&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
中小型应用；
&lt;/li&gt;
&lt;li&gt;
想要简单高效状态管理的场景；
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
API 极简，零模板代码
&lt;/li&gt;
&lt;li&gt;
性能好，支持局部订阅；
&lt;/li&gt;
&lt;li&gt;
支持 hooks，易于集成
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
社区相对较小；
&lt;/li&gt;
&lt;li&gt;
功能不如 Redux 完善，如中间件、调试工具等较弱
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jotai&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;原子粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极简&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有（Jotai DevTools）&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;新兴&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中&lt;/td&gt;
&lt;td&gt;函数式、原子化、极简&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
轻量级项目；
&lt;/li&gt;
&lt;li&gt;
需要响应式原子状态的场景
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
响应式原子状态，API 简单；
&lt;/li&gt;
&lt;li&gt;
体积小，性能优秀
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
社区还在成长；
&lt;/li&gt;
&lt;li&gt;
功能相对精简
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XState&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;状态机/流程&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;复杂&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;中/大&lt;/td&gt;
&lt;td&gt;复杂业务流程、可视化&lt;/td&gt;
&lt;td&gt;多状态流转、流程编排、复杂异步与副作用管理&lt;/td&gt;
&lt;td&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;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
学习曲线高；心智模型重；
&lt;/li&gt;
&lt;li&gt;
在简单场景偏重
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preact Signals&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极细粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极简&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;好&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有限&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;新兴&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中&lt;/td&gt;
&lt;td&gt;微秒级响应、性能极致&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
极致性能需求的组件局部状态；
&lt;/li&gt;
&lt;li&gt;
频繁更新的 UI
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
超高性能；
&lt;/li&gt;
&lt;li&gt;
信号机制带来精准更新；
&lt;/li&gt;
&lt;li&gt;
极小 API
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
生态有限；
&lt;/li&gt;
&lt;li&gt;
DevTools 支持弱；
&lt;/li&gt;
&lt;li&gt;
需手动做异步与持久化方案
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinia&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;原子/模块粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;简单&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;支持&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;成熟&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中/大&lt;/td&gt;
&lt;td&gt;Vue 原生、社区有 React 适配&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
跨框架（主要 Vue）或在微前端中复用；
&lt;/li&gt;
&lt;li&gt;
模块化状态
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
API 直观；
&lt;/li&gt;
&lt;li&gt;
模块/插件生态好；
&lt;/li&gt;
&lt;li&gt;
类型友好
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;
在纯 React 项目里并非主流；
&lt;/li&gt;
&lt;li&gt;
适配层可能增加复杂度
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;helux&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极细粒度&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;丰富&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极高&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;极好&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;完善&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有限/需手动&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;有&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;新兴&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;小/中&lt;/td&gt;
&lt;td&gt;响应式+按需渲染、类型友好&lt;/td&gt;
&lt;td&gt;需要极细粒度渲染优化与强类型结合场景&lt;/td&gt;
&lt;td&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;/td&gt;
&lt;td&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;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&quot;对比&quot;&gt;&lt;a href=&quot;#%E5%AF%B9%E6%AF%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;对比&lt;/h1&gt;
&lt;h2 id=&quot;代码&quot;&gt;&lt;a href=&quot;#%E4%BB%A3%E7%A0%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;代码&lt;/h2&gt;
&lt;h3 id=&quot;context-api&quot;&gt;&lt;a href=&quot;#context-api&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Context API&lt;/h3&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;39545563457810700000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import React, { createContext, useContext, useState } from &apos;react&apos;;

const DemoContext = createContext();

const DemoProvider = ({ children }) =&gt; {
   const [count, setCount] = useState(0);
   const [name, setName] = useState(&apos;Anonymous&apos;);

   return &lt;DemoContext.Provider value={{ count, setCount, name, setName }}&gt;{children}&lt;/DemoContext.Provider&gt;;
};

const GrandSonName = () =&gt; {
   const { name } = useContext(DemoContext);
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos; }}&gt;
         &lt;h3&gt;GrandSonName&lt;/h3&gt;
         &lt;p&gt;name: {name}&lt;/p&gt;
      &lt;/div&gt;
   );
};

const GrandSonCount = () =&gt; {
   const { count } = useContext(DemoContext);
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos; }}&gt;
         &lt;h3&gt;GrandSonCount&lt;/h3&gt;
         &lt;p&gt;count: {count}&lt;/p&gt;
      &lt;/div&gt;
   );
};

const Son1 = () =&gt; {
   const { count, setCount, name } = useContext(DemoContext);

   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5 }}&gt;
         &lt;h2&gt;Child 1&lt;/h2&gt;
         &lt;p&gt;Count: {count}&lt;/p&gt;
         &lt;p&gt;Name: {name}&lt;/p&gt;
         &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment Count&lt;/button&gt;
         &lt;GrandSonName /&gt;
         &lt;GrandSonCount /&gt;
      &lt;/div&gt;
   );
};

const Son2 = () =&gt; {
   const { name, setName } = useContext(DemoContext);

   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5, marginTop: 10 }}&gt;
         &lt;h2&gt;Child 2&lt;/h2&gt;
         &lt;input type=&apos;text&apos; value={name} onChange={(e) =&gt; setName(e.target.value)} /&gt;
         &lt;GrandSonName /&gt;
      &lt;/div&gt;
   );
};

const Son3 = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5, marginTop: 10 }}&gt;
         &lt;h2&gt;Child 3&lt;/h2&gt;
         &lt;GrandSonName /&gt;
         &lt;GrandSonCount /&gt;
      &lt;/div&gt;
   );
};

export default function App() {
   return (
      &lt;DemoProvider&gt;
         &lt;Son1 /&gt;
         &lt;Son2 /&gt;
         &lt;Son3 /&gt;
      &lt;/DemoProvider&gt;
   );
}`, `39545563457810700000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;jsx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;jsx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; DemoContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;DemoProvider&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Anonymous&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DemoContext.Provider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DemoContext.Provider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;GrandSonName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DemoContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;GrandSonName&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;GrandSonCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DemoContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;GrandSonCount&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DemoContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 1&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Count: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Name: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Increment Count&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonCount&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DemoContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; marginTop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 2&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; marginTop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 3&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonCount&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DemoProvider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son1&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son2&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son3&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DemoProvider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;helux&quot;&gt;&lt;a href=&quot;#helux&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;helux&lt;/h3&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61760289166970300000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { sharex, \$, useAtom } from &apos;helux&apos;;
import React from &apos;react&apos;;

const GlobalModel = sharex({
   count: 1,
   name: &apos;Anonymous&apos;
});

const { state, setState } = GlobalModel;

const GrandSonName = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos; }}&gt;
         &lt;h3&gt;GrandSonName&lt;/h3&gt;
         &lt;p&gt;name: {\$(state.name)}&lt;/p&gt;
      &lt;/div&gt;
   );
};

const GrandSonCount = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos; }}&gt;
         &lt;h3&gt;GrandSonCount&lt;/h3&gt;
         &lt;p&gt;count: {\$(state.count)}&lt;/p&gt;
      &lt;/div&gt;
   );
};

const Son1 = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5 }}&gt;
         &lt;h2&gt;Child 1&lt;/h2&gt;
         &lt;p&gt;Count: {\$(state.count)}&lt;/p&gt;
         &lt;p&gt;Name: {\$(state.name)}&lt;/p&gt;
         &lt;button
            onClick={() =&gt;
               setState((draft) =&gt; {
                  draft.count += 1;
               })
            }
         &gt;
            Increment Count
         &lt;/button&gt;
         &lt;GrandSonName /&gt;
         &lt;GrandSonCount /&gt;
      &lt;/div&gt;
   );
};

// 这里需要抽出一个组件，否则依然会有性能问题
const Input = () =&gt; {
   const [dictState] = useAtom(state);
   return (
      &lt;input
         type=&apos;text&apos;
         value={dictState.name}
         onChange={(e) =&gt; {
            setState((draft) =&gt; {
               draft.name = e.target.value;
            });
         }}
      /&gt;
   );
};

const Son2 = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5, marginTop: 10 }}&gt;
         &lt;h2&gt;Child 2&lt;/h2&gt;
         &lt;Input /&gt;
         &lt;GrandSonName /&gt;
      &lt;/div&gt;
   );
};

const Son3 = () =&gt; {
   return (
      &lt;div style={{ border: &apos;1px #333 solid&apos;, padding: 5, marginTop: 10 }}&gt;
         &lt;h2&gt;Child 3&lt;/h2&gt;
         &lt;GrandSonName /&gt;
         &lt;GrandSonCount /&gt;
      &lt;/div&gt;
   );
};

export default function App() {
   return (
      &lt;&gt;
         &lt;Son1 /&gt;
         &lt;Son2 /&gt;
         &lt;Son3 /&gt;
      &lt;/&gt;
   );
}`, `61760289166970300000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;jsx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;jsx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sharex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useAtom &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;helux&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; GlobalModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sharex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   count&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Anonymous&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; GlobalModel&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;GrandSonName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;GrandSonName&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;GrandSonCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;GrandSonCount&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 1&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Count: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Name: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  draft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            Increment Count
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonCount&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 这里需要抽出一个组件，否则依然会有性能问题&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dictState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useAtom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;dictState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               draft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; marginTop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 2&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Son3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1px #333 solid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; padding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; marginTop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Child 3&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonName&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GrandSonCount&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son1&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son2&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Son3&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;效果&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状态库&lt;/th&gt;
&lt;th&gt;React 层重渲染&lt;/th&gt;
&lt;th&gt;浏览器重渲染&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context API&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;video style=&quot;max-width: 200px&quot; data-src=&quot;/examples/react-global-state-selection/1.mp4&quot; loop=&quot;&quot; muted=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; class=&quot;lazy&quot;&gt;&lt;/video&gt;&lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;video style=&quot;max-width: 200px&quot; data-src=&quot;/examples/react-global-state-selection/2.mp4&quot; loop=&quot;&quot; muted=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; class=&quot;lazy&quot;&gt;&lt;/video&gt;&lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;helux&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;video style=&quot;max-width: 200px&quot; data-src=&quot;/examples/react-global-state-selection/3.mp4&quot; loop=&quot;&quot; muted=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; class=&quot;lazy&quot;&gt;&lt;/video&gt;&lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;td&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;video style=&quot;max-width: 200px&quot; data-src=&quot;/examples/react-global-state-selection/4.mp4&quot; loop=&quot;&quot; muted=&quot;&quot; autoplay=&quot;&quot; width=&quot;100%&quot; class=&quot;lazy&quot;&gt;&lt;/video&gt;&lt;/body&gt;&lt;/html&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&quot;结论&quot;&gt;&lt;a href=&quot;#%E7%BB%93%E8%AE%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;结论&lt;/h1&gt;
&lt;p&gt;以 React 层重渲染（即组件是否触发 render）为准，此标准比浏览器重渲染更高，由上面的视频可得出以下结论&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;虽然最终浏览器重渲染的效果一样，但 helux 对比 Context API 明显在 React 层性能更好，归功于 helux 的 dom 粒度更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;helux 的 signal 语法只适用于展示型即普通的 dom 元素，无法适用于 react 组件，需要使用 useAtom 转换为真实状态，但此时就失去了 dom 粒度更新的优势，需要将 react 组件抽出一个单独组件防止性能劣化&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-2517a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 66.70190274841438%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABV0lEQVQoz5VRi2rCUAztb4z1vl+5r97WWq06piJujLH//52lVoTBcO4QckvJyUlOKhcTFYxqjlFzQiQjimNQzWrOCKF3UCkfSszvm9XbdnkY+kObjl04LvJLVzbOSMbu0CsdUrGwb8dlLJ2PC2vXYAaAhYu9lH+QbcyE0aF0p3G3S6kHP/gwaC1J/VTfHRrJLmXOee/Dx+l8Opx3q822H1Y+eqUYglL2G65kQLKQXAhoCpTWpCLBy9h069E6hxVCCKUkZjmlK+YWlQkRyVorJnDSml78ZfQh4M5JKkkuoP9EZUMUBgC8s0oILjiqTm3mZvPHVIh5ip9kyFlIE1MXG7xOhNQoY53Vbds6hdtqm4oyRoM3IRtU0PK2VhVKeaZsDOFz//q1HvqMl0N9LpXCKfAQQio+PWLylfOb1RPZl4LFAXxqcvCg5/0fHBuV8Qb4syZo9v9s+wYKvGpzilWPowAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 14 52 50&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-fee1c.png&quot; data-srcset=&quot;/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-a67b7.png 200w,
/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-0b187.png 400w,
/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-fee1c.png 800w,
/static/2025-10-23-14-52-50-918985131cae7518d0d4bfd63ac071fe-2517a.png 946w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;快速上手&quot;&gt;&lt;a href=&quot;#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;快速上手&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://heluxjs.github.io/helux/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://heluxjs.github.io/helux/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;全局状态&quot;&gt;&lt;a href=&quot;#%E5%85%A8%E5%B1%80%E7%8A%B6%E6%80%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;全局状态&lt;/h2&gt;
&lt;p&gt;文件夹结构&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;12472299876994253000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`├── model
│   ├── actions.js
│   ├── index.js
│   └── state.js`, `12472299876994253000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;├── model
│   ├── actions.js
│   ├── index.js
│   └── state.js&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;index.js&lt;/p&gt;
&lt;p&gt;导出主入口&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58077587738954390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { \$, useAtom } from &apos;helux&apos;;

import { GlobalState } from &apos;./state&apos;;
import * as actions from &apos;./actions&apos;; // action 函数定义

export default GlobalState;

export const action = GlobalState.defineActions()(actions);

const { state, setState } = GlobalState;

export { \$, useAtom, state, setState };`, `58077587738954390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useAtom &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;helux&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; GlobalState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./state&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; actions &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./actions&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// action 函数定义&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; GlobalState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; GlobalState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defineActions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; GlobalState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useAtom&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;state.js&lt;/p&gt;
&lt;p&gt;全局状态定义&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;95946723239182780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { sharex } from &apos;helux&apos;;

export const GlobalState = sharex(
   {
      key: null,
      key2: null,
      jobInfo: {}
   },
   { moduleName: &apos;JobDetailState&apos; }
);`, `95946723239182780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sharex &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;helux&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; GlobalState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sharex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      key2&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      jobInfo&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; moduleName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;JobDetailState&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;actions.js&lt;/p&gt;
&lt;p&gt;异步 action 定义&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84809737832789180000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { getJobInfo } from &apos;../services&apos;;

export async function queryJobInfo({ draft, payload }) {
   const [error, res] = await getJobInfo({ id: payload.jobId });
   if (error) {
      return null;
   }
   draft.jobInfo = res;
   return res;
}`, `84809737832789180000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getJobInfo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;../services&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryJobInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; draft&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; payload &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getJobInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jobId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   draft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jobInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;纯展示类型&quot;&gt;&lt;a href=&quot;#%E7%BA%AF%E5%B1%95%E7%A4%BA%E7%B1%BB%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;纯展示类型&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84451686898317900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { \$, state, setState } from &apos;./model&apos;;

// 纯展示型即 dom 结点直接使用 signal 语法，即 \$(state.key)
export default function Example() {
   return (
      &lt;div&gt;
         &lt;Button
            onClick={() =&gt;
               setState((draft) =&gt; {
                  draft.key += 1;
               })
            }
         &gt;
            {\$(state.key)}
         &lt;/Button&gt;
      &lt;/div&gt;
   );
}`, `84451686898317900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;jsx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;jsx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./model&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 纯展示型即 dom 结点直接使用 signal 语法，即 $(state.key)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  draft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;react-状态类型&quot;&gt;&lt;a href=&quot;#react-%E7%8A%B6%E6%80%81%E7%B1%BB%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;React 状态类型&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;7301945611234006000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { \$, state, setState, useAtom } from &apos;./model&apos;;

// 这里需要抽出一个组件隔离渲染，即 useAtom 会触发组件重渲染
const Input = () =&gt; {
   const [dictState] = useAtom(state); // 由于下面使用了 key2，更新 key2 时才会触发重渲染，即更新 key 时不会触发重渲染
   return (
      &lt;input
         type=&apos;text&apos;
         value={dictState.key2}
         onChange={(e) =&gt; {
            setState((draft) =&gt; {
               draft.key2 = e.target.value;
            });
         }}
      /&gt;
   );
};

// React 组件由于需要取到原始值，需要使用 useAtom 包一下取出
export default function Example2() {
   return (
      &lt;div&gt;
         &lt;Input /&gt;
         {\$(state.key2)}
      &lt;/div&gt;
   );
}`, `7301945611234006000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;jsx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;jsx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useAtom &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./model&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 这里需要抽出一个组件隔离渲染，即 useAtom 会触发组件重渲染&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dictState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useAtom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 由于下面使用了 key2，更新 key2 时才会触发重渲染，即更新 key 时不会触发重渲染&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;dictState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key2&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               draft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// React 组件由于需要取到原始值，需要使用 useAtom 包一下取出&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Example2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;异步请求类型&quot;&gt;&lt;a href=&quot;#%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%B1%BB%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;异步请求类型&lt;/h2&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;写成 action 也有利于调试，安装 &lt;a href=&quot;https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Redux DevTools 插件&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-a9fd5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 52.96875%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABVklEQVQoz42QvUvDQBjG+6cougjSTVFwUEoHP8BKFdQ66SA4+d8I7pZ2kGqyilMGQXBqB6famqSlVtPc5e6SyyXx7SVI+qH48C539/7ueZ83E4ggGpUQgY2JZeNhDRBzfV+E0TRl/EgQ6mCEMMa2bXueN9kUSraH/Nvnr+rTp/aKE5iGvPPRbzZbumHqug78sFvWGNzue9ePvauHnvpiJTBYWo6HmYCSfeGEbaIpYzOXU+YiTAhlYGtZAzYqX0oIAbgIwrgSOP2Tpmm12p2qqkosVb1XlEqlelMuwyFeR3qEzE88uHUwLA4RQmB54Mk5f9eNw9Lpylr+/OKSMjYOp7NRKWBgTvgFUhimmcvvzsxlC8USBPsVhlSxJzBg22g06vV6p9stFI+XVjeOSmcYO385A+ykxLn31mqv57Zn57NbOwfw+l9YxqaQOb9ZXFhc3ts/mYS/AZ4ZWMKvhvwfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 16 21 40&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-fee1c.png&quot; data-srcset=&quot;/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-a67b7.png 200w,
/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-0b187.png 400w,
/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-fee1c.png 800w,
/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-b1a91.png 1200w,
/static/2025-10-23-16-21-40-d54ab074942669dd475a45e76f6f881e-a9fd5.png 1280w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-055ed.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 119.09604519774012%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsSAAALEgHS3X78AAACQElEQVQ4y51U75OTMBDlP1ex7TnV8aanp//R+fE+6gfHTvlRIAkpkEBIIOAL6MzVoZ71DbNkIG/fZje73ufb7e7u9v7j7sPu/af7Oyzevb1Zr19t3ONvNv7Nxtn12l+t/O12g+fN6qXvv/Bf+174fR8nKcvzoiyLsiqrClbKulGqUe1sZV3zQhRCadO12jRNk6RZmlGPPT4mhJRFcTqdKocSC6UU55wxxidkWRbHMWM0TdI8Z8W0mfPcA8Pa3pgO6Pt+HMcaOpwTQpIkmckce4sCb7iglM5kwMMnFzBQFogHZCkEpIQQ+CKLoqtrU0sDKyVE7DBAo7e9tdYjhOY5p4QiyEoIR5YSZDnhlOd921qtB2tBgh2fwIN6WVawiLYz3TAMSA9iAxO+jTG9NrbrxiVAmUDZ6XMO/qyM04oJSPdgNDwuk13KKSUTGGVN4/IcRRHyDl/B4aCkHC/ASx2ZwQXIyBBEQDsej7DISxiFtVLYtyjtTQX6BTvlAwscFbJYwIuoLytf+jGTwzDkJ94qBXdXk3H4OIqCIEA9rybHcQTx/X6fkmz4dzIKhgsbBg5ZllaiGq8lZxPqplks9d/Cdq2XJJRRrO35xXyejFLP+v9TKq01xNFVdqlOz5CFlFopkPvpkl1BbtGJS+c8IyON9hx976YKTmu07ib8sWGiuJHgYQa4oYFB8hv44losy4hrFjLPtvIJMJEwFx8evni4PWhJ8Kfph1kisBstyhzyefrBzn8xNppafDvkX3+kcRT8BP+TVbxbS3rAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 10 23 16 22 01&quot; title=&quot;&quot; data-src=&quot;/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-fee1c.png&quot; data-srcset=&quot;/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-a67b7.png 200w,
/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-0b187.png 400w,
/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-fee1c.png 800w,
/static/2025-10-23-16-22-01-e038724b4b2b091ce38af2aef53bc906-055ed.png 885w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;48702171265951840000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { \$, state, setState, action } from &apos;./model&apos;;

const JobDetail = ({ jobId }) =&gt; {
   // const { queryJobInfo } = action.useLoading();
   // console.log(&apos;useLoading&apos;, queryJobInfo) // 请求的状态

   const handleEvent = () =&gt; {
      console.log(&apos;state.jobId&apos;, state.jobInfo); // 事件触发的可以直接通过 state 拿到最新值
   };

   return (
      &lt;div className={styles.container}&gt;
         &lt;Button
            onClick={() =&gt;
               setState(async (draft) =&gt; {
                  const data = await action.eActions.queryJobInfo({ jobId });
                  console.log(&apos;data&apos;, data);
               })
            }
         &gt;
            {\$(state.failedFNGPMetrics.total)} // 用 \$ 或者 useAtom 取到
         &lt;/Button&gt;
      &lt;/div&gt;
   );
};

export default JobDetail;`, `48702171265951840000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;jsx&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;jsx&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;jsx&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-jsx line-numbers&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./model&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;JobDetail&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; jobId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// const { queryJobInfo } = action.useLoading();&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// console.log(&apos;useLoading&apos;, queryJobInfo) // 请求的状态&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;state.jobId&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jobInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 事件触发的可以直接通过 state 拿到最新值&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;styles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eActions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryJobInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; jobId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failedFNGPMetrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; // 用 $ 或者 useAtom 取到
         &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; JobDetail&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;参考链接&quot;&gt;&lt;a href=&quot;#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;参考链接&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7172509907403407391&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;全都要!一文带你全面体验八种状态管理库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7322840234365059084&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;你踩过 react 生态的 signal 坑吗?且看 helux 如何应对&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7399276698619035657&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Redux、Zustand、Mobx、Valtio、Recoil、jotai、XState 状态管理怎么选&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[前后端调试工具应用]]></title><description><![CDATA[前端远程调试 背景 由于有些时候不方便到用户实地/远程桌面复现 bug，需要远程查看对方网页的控制台，经过预研发现远程调试工具  page-spy-web…]]></description><link>https://blog.towavephone.com/frontend-backend-debug-tool/</link><guid isPermaLink="false">https://blog.towavephone.com/frontend-backend-debug-tool/</guid><pubDate>Thu, 17 Apr 2025 18:49:44 GMT</pubDate><content:encoded>&lt;h1 id=&quot;前端远程调试&quot;&gt;&lt;a href=&quot;#%E5%89%8D%E7%AB%AF%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;前端远程调试&lt;/h1&gt;
&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h2&gt;
&lt;p&gt;由于有些时候不方便到用户实地/远程桌面复现 bug，需要远程查看对方网页的控制台，经过预研发现远程调试工具 &lt;a href=&quot;https://github.com/HuolalaTech/page-spy-web/tree/main&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;page-spy-web&lt;/a&gt; 比较合适&lt;/p&gt;
&lt;h2 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在开发机（由于开发只能完全控制开发机，同时要保证此开发机能被下面的前端服务器访问）上部署一个 &lt;a href=&quot;https://github.com/HuolalaTech/page-spy-web/blob/main/README_ZH.md&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;docker 镜像&lt;/a&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26703368151396446000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`docker run -d --restart=always -p 6752:6752 --name=&amp;quot;pageSpy&amp;quot; ghcr.io/huolalatech/page-spy-web:latest`, `26703368151396446000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d --restart&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;always -p &lt;span class=&quot;token number&quot;&gt;6752&lt;/span&gt;:6752 --name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pageSpy&quot;&lt;/span&gt; ghcr.io/huolalatech/page-spy-web:latest&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过特定域名（可以自定义域名后缀，配合下面的前端服务器），配置 nginx 转发到此开发机&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13893101863120472000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`server {
  listen 80;
  server_name *.你的对外域名后缀（和前端域名保持一致后缀）;
  access_log  /var/log/nginx/access.log main;
  proxy_read_timeout 240s;

  location / {
     proxy_http_version 1.1;
     proxy_set_header Upgrade \$http_upgrade;
     proxy_set_header Connection &amp;quot;Upgrade&amp;quot;;
     proxy_set_header Host \$host;
     proxy_pass http://localhost:6752;
  }
}`, `13893101863120472000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;server &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  listen &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  server_name *.你的对外域名后缀（和前端域名保持一致后缀）&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  access_log  /var/log/nginx/access.log main&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  proxy_read_timeout 240s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  location / &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     proxy_http_version &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header Upgrade &lt;span class=&quot;token variable&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header Connection &lt;span class=&quot;token string&quot;&gt;&quot;Upgrade&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header Host &lt;span class=&quot;token variable&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_pass http://localhost:6752&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由于前端服务器需要能访问到开发机，所以需要在前端服务器对应的 nginx 做转发，这里配置到前端域名的子路径，参考&lt;a href=&quot;https://github.com/HuolalaTech/page-spy-web/wiki/%F0%9F%90%9E-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94#%E5%A6%82%E4%BD%95%E9%83%A8%E7%BD%B2%E5%88%B0%E5%AD%90%E8%B7%AF%E5%BE%84&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;步骤&lt;/a&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92836356858242940000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`server {
  location /pagespy/  {
     # 这里请求转发到开发机，同时由于开发机的 nginx 配置的和前端域名一致，所以能成功转发到开发机对应的 pagespy 服务
     rewrite ^/pagespy/(.*)\$ /\$1 break;
     proxy_pass  http://开发机对外ip;
     proxy_http_version 1.1;
     proxy_set_header  Upgrade \$http_upgrade;
     proxy_set_header  Connection &amp;quot;upgrade&amp;quot;;
     proxy_set_header  Host \$host;
     proxy_set_header  X-Real-IP \$remote_addr;
     proxy_set_header  X-Forwarded-For \$proxy_add_x_forwarded_for;
  }

  location /pagespy {
     return 301 \$scheme://\$host\$request_uri/;
  }
}`, `92836356858242940000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;server &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  location /pagespy/  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# 这里请求转发到开发机，同时由于开发机的 nginx 配置的和前端域名一致，所以能成功转发到开发机对应的 pagespy 服务&lt;/span&gt;
     rewrite ^/pagespy/&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;.*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;$ /&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_pass  http://开发机对外ip&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_http_version &lt;span class=&quot;token number&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header  Upgrade &lt;span class=&quot;token variable&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header  Connection &lt;span class=&quot;token string&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header  Host &lt;span class=&quot;token variable&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header  X-Real-IP &lt;span class=&quot;token variable&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     proxy_set_header  X-Forwarded-For &lt;span class=&quot;token variable&quot;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  location /pagespy &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token builtin class-name&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;301&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scheme&lt;/span&gt;://&lt;span class=&quot;token variable&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$request_uri&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改前端代码主入口适配远程调试&lt;/p&gt;
&lt;p&gt;src/utils/index.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;67011367569290980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 异步加载单个脚本
async function loadSingleScript(src, options) {
  return await new Promise((resolve, reject) =&gt; {
     // 这里未考虑到同一 src 同时发起的情况，改用缓存实现
     // if (!id) {
     //   const NAMESPACE = &apos;c2b16a16-12b3-423a-879f-6b46d1a01d60&apos;
     //   const PREFIX = &apos;script-id-&apos;
     //   id = PREFIX + uuidv5(src, NAMESPACE)
     // }
     // if (!src || document.querySelector(\`#\${id}\`)) {
     //   return
     // }
     const script = document.createElement(&apos;script&apos;);
     const { attributesMap = {}, ...rest } = options;
     Object.keys(rest).forEach((key) =&gt; {
        script[key] = rest[key];
     });
     Object.keys(attributesMap).forEach((key) =&gt; {
        script.setAttribute(key, attributesMap[key]);
     });
     script.async = true;
     script.src = src;
     script.onload = resolve;
     script.onerror = reject;
     document.getElementsByTagName(&apos;head&apos;)[0].appendChild(script);
  });
}

// 异步加载多个脚本
// memoize 默认情况下用第一个参数作为缓存的 key，即 src
export const loadScript = memoize(loadSingleScript);`, `67011367569290980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 异步加载单个脚本&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadSingleScript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// 这里未考虑到同一 src 同时发起的情况，改用缓存实现&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// if (!id) {&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;//   const NAMESPACE = &apos;c2b16a16-12b3-423a-879f-6b46d1a01d60&apos;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;//   const PREFIX = &apos;script-id-&apos;&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;//   id = PREFIX + uuidv5(src, NAMESPACE)&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// }&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// if (!src || document.querySelector(`#${id}`)) {&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;//   return&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// }&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; script &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;script&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; attributesMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        script&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rest&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;attributesMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributesMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;async &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resolve&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onerror &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementsByTagName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;head&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 异步加载多个脚本&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// memoize 默认情况下用第一个参数作为缓存的 key，即 src&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; loadScript &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;memoize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadSingleScript&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;src/index.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88944902327417980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import { loadScript } from &apos;./utils&apos;;

async function loadScriptFunc() {
  // 这里使用别名访问 pagespy 服务
  await loadScript(&apos;/pagespy/page-spy/index.min.js&apos;, { attributesMap: { crossorigin: &apos;anonymous&apos; } });
  if (!window.PageSpy) {
     return;
  }
  window.\$pageSpy = new window.PageSpy({
     api: window.location.host + &apos;/pagespy&apos;,
     clientOrigin: window.location.origin + &apos;/pagespy&apos;,
     project: window.location.host,
     title: window.localStorage.getItem(&apos;simUsername&apos;) || &apos;anonymous&apos;
  });
}

const is_debug = new URLSearchParams(window.location.hash.split(&apos;?&apos;)[1]).get(&apos;is_debug&apos;);

if (process.env.NODE_ENV === &apos;production&apos; &amp;&amp; is_debug) {
  loadScriptFunc();
}`, `88944902327417980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; loadScript &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./utils&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadScriptFunc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 这里使用别名访问 pagespy 服务&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadScript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/pagespy/page-spy/index.min.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; attributesMap&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; crossorigin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;anonymous&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PageSpy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;$pageSpy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PageSpy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     api&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;host &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/pagespy&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     clientOrigin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/pagespy&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     project&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;host&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;simUsername&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;anonymous&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; is_debug &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;?&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;is_debug&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;production&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; is_debug&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;loadScriptFunc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;效果&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;让需要调试的远程页面加上 &lt;code class=&quot;language-text&quot;&gt;?is_debug=1&lt;/code&gt; 参数，可以看到左下角进入待调试状态，点击左下角按钮然后点击 copy 复制出来待调试的远程链接，如果不方便让用户复制，转步骤 2&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-16-11-03-42-7bb8f8dbc780c4d008ab7c3b02979e82-47ad6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 78px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 105.12820512820514%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAACXBIWXMAAAsSAAALEgHS3X78AAAB4UlEQVQ4y5VU207CQBDtZ5gYY3zxwWgk6iNq4oUoiRjUGK/xEgkaoFdaoHa73QJ+t2d36lookngeYDo7Zy5ntjVihSiK4n+CMWb8RWYKkQLZRf4MMuxPhSgH7ZlHRkQYhlSt2Aul0EcGeSkaNJzNHE8TEDkYDOjR0LPNYQqRpEORn4v4Wdt9hSITDQ3HqdnyKuVbFrGYZ/6BgkGZgiAoCi6ZI+HawerC4dnxo0gTrQPK9no9SUbDsDjnhZrCsfy1xZP66ctonE6lzipTz3kyEqOmYwYbS7Xa0WvMeCI4ldXioaQko2dYmoyjJOF2p1davjrZfdnfelxfvGy+uukw0XwaVqrt+z50/iVHbPQl6pXm9srtfunpYOd5c+n65rw1GgupWU7zjExtsx/whHt2v7r3dnX6US03LirvUwMjGPUycrfbpcrUktxtCge/O+/c1ztCSDtmE2TUkzNDatu2i3tqPLjNZw9MrHfqsoLseZ5BN9ayrLzg+Ped0DNDKFe8OQjDnhzHyS4J2gYfRk62mM8gxiQNOgXF0BtHJrikWjNJP0z8UmS2Z33X4TJNEwb/AyBYCphx4kuCB6rfbreRBSrS20+iINp1XTqCTS/CxMeA4rA55G4rdBTIhhNHKK7f7W+yYVMJjySfhwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 16 11 03 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-16-11-03-42-7bb8f8dbc780c4d008ab7c3b02979e82-47ad6.png&quot; data-srcset=&quot;/static/2024-05-16-11-03-42-7bb8f8dbc780c4d008ab7c3b02979e82-47ad6.png 78w&quot; data-sizes=&quot;(max-width: 78px) 100vw, 78px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-16-11-03-29-f6cc133339429b322dde7d38f5047470-83562.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 315px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.19047619047619%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABaklEQVQoz41RXW+CMBTt//8jLvF9PuiTwhxqYgwyJ0ihUHELM/L9KWOHwsN886S5ub0959zblozH4+12qyjK6mmADMlo9EI2m03btk3TtE+jJ8uyRNbrVRzHsizv95ouoGkfaZoej7pl2UmS3O/3uqq6VSNUeZ7jFOLXyYRISwVZEITQgK1pmmmaZVl6nsf5+UvAdbnjOJzzy+UCfVEWkMznc7J878RgqOoe3dCfUgp7Si1HAF7UspjNLIHb7VYUnXixWBDcHhmmhexw+ATVcVwcow+ag80Y830f7tfr9VsAcw3ilRDHcYKxVVXd7XaGYUBsM3Y6mcgRXc6pSUHQdQNt4ALJdDodxsac8MPL4TGyLEMuHiYTu7wQSJIUxUQAktlsRqS3916M42oAtCVi81tjDfuuUvWxv7MkdV+1RoZq/Q/4HdDONDhbAZL6Ef2d8bsEBjALwzB+RBTFnvPDbT8Mo8d6BDIsMPYfvLhW1+5TuC0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 16 11 03 29&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-16-11-03-29-f6cc133339429b322dde7d38f5047470-83562.png&quot; data-srcset=&quot;/static/2024-05-16-11-03-29-f6cc133339429b322dde7d38f5047470-738c0.png 200w,
/static/2024-05-16-11-03-29-f6cc133339429b322dde7d38f5047470-83562.png 315w&quot; data-sizes=&quot;(max-width: 315px) 100vw, 315px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-7f18a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.46987951807229%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAA5UlEQVQoz52Sa5LCIBCEuf/Z9hD+SqKyGnkOz3ZCRKNV1uqm8hVMQzo9FMKrgnE44iCPiDGilPIxuZEbXRPDMCJQgNcKzjlorWGMgV7g+TsUM+vLymW+60JOO5x+J4y7H8RAsM6D2Jh4MYaAlBISJ2/jK/HGRhNeB8jDCZ4ijDZQysArDdpPKCmjP7VWPIr2bqj3PcLMASWiYa3lhJzOWJCUiNx2Yi0Ttc2fIJyzKLW0/yxn6Dwh8BjO52aWeJ659S8MHWpZi+UMevSnFjfan4bLVelFzvnx8RcmT4Y9QbtXW8N/cgVTEcN13/j7JAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 16 11 08 15&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-fee1c.png&quot; data-srcset=&quot;/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-a67b7.png 200w,
/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-0b187.png 400w,
/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-fee1c.png 800w,
/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-b1a91.png 1200w,
/static/2024-05-16-11-08-15-23fa931a67f570e50ce463e2f81ee53c-7f18a.png 1245w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;进入调试界面，网址为前端域名加上 /pagespy/，找到对应的项目/用户名/设备号（设备号可让用户截图发过来避免调试错误页面的情况）进入房间列表去调试&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-a9fd5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.09375000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABTklEQVQoz4WSPU/DMBCG8xuiJP5MUtw6qUGoNI3SCEVI0IER0Q2GTgjEwP8fX+y0qWLR0uFk++783Hv2BXmqUegSQnBQSsEY+9dcjpAS80mO1XSKWZ4jzTKkaQqtNQLKOAghF0D0YKfjnHNEUYT1eo2Ac9k7zpmw8VTkkDy3Z+HF2JAjBOI4RtM0CJgDukouwasqkJAYV7JEW2xxr3Z2b0BZvAcelLl1AB4VDrL5AbpPiNDULXbbH7x033i4eUOtXqHl6ggd7rl813LXdb5Cvx0GY65xd7eEnpXgJMNEaqhJ0b+n9yyXgK6yFNYvGeriGZv6HZ9fH3jaPIJQ0gNcwfPAUdBTaZXMslsYtULbtpiXcyQJ8QqfBI5nzI1QkiRHi+KwtzAM+zMffd5Jhe5TpB3UzA7n1A6qMQaLxQJVVWFZLfu98yml9q2yv92Mgb8ZGw/gKUxbggAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 16 11 01 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-fee1c.png&quot; data-srcset=&quot;/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-a67b7.png 200w,
/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-0b187.png 400w,
/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-fee1c.png 800w,
/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-b1a91.png 1200w,
/static/2024-05-16-11-01-42-c62d5f073bf24e89b39c7a25a86fe74b-a9fd5.png 1280w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-2407e.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 34.28209993674889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsSAAALEgHS3X78AAAAw0lEQVQY03WPTQvCMAyG9///kBdP3gUPIsz5cZgwFV3b5aNrt4lpuymIewihafrmTbPqWllru65jZu99N49zjiNEJBkRs1Qbo/M811rLoHSDEZ6IFUjXtiGSPqOInOq6loOYi4NkKWWWnwAAY4xMJgyR/EdxAqAhptQQI/oByToqi+a8U9ziKJZ3ss4wvA7FUSR93/9RfiZAiET4M4ZV+H7h6kTl/gEKg/esnOJmUWw5/F723a7UZqnWi+etBOt43vvLGz0MkDjnxXr/AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 16 11 07 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-fee1c.png&quot; data-srcset=&quot;/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-a67b7.png 200w,
/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-0b187.png 400w,
/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-fee1c.png 800w,
/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-b1a91.png 1200w,
/static/2024-05-16-11-07-05-e0a887dd5aa70ca72c948b8b0b521022-2407e.png 1581w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h1 id=&quot;后端调试&quot;&gt;&lt;a href=&quot;#%E5%90%8E%E7%AB%AF%E8%B0%83%E8%AF%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;后端调试&lt;/h1&gt;
&lt;h2 id=&quot;背景-1&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h2&gt;
&lt;p&gt;对于 python 后端框架，在内部逻辑有执行 &lt;code class=&quot;language-text&quot;&gt;orm&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;原生 sql&lt;/code&gt; 的接口的过程中不知道具体执行了什么 sql，需要有一个调试工具能展示&lt;/p&gt;
&lt;h2 id=&quot;django&quot;&gt;&lt;a href=&quot;#django&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django&lt;/h2&gt;
&lt;p&gt;针对后端使用的 Django 3.1 以及 python 3.6 版本，预研发现只有 &lt;a href=&quot;https://github.com/jazzband/django-debug-toolbar&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;django-debug-toolbar&lt;/a&gt;，&lt;a href=&quot;https://github.com/jazzband/django-silk&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;django-silk&lt;/a&gt; 符合要求&lt;/p&gt;
&lt;h3 id=&quot;实现-1&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h3&gt;
&lt;h4 id=&quot;django-debug-toolbar--django-silk&quot;&gt;&lt;a href=&quot;#django-debug-toolbar--django-silk&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django-debug-toolbar + django-silk&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装依赖库&lt;/p&gt;
&lt;p&gt;Dockerfile-local&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;633725856908906600&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`RUN python3.6 -m pip install django-silk==4.2.0 django-debug-toolbar==3.1.1 ipython pyflame git+https://github.com/towavephone/django-debug-toolbar-mongo.git@v0.3`, `633725856908906600`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;dockerfile&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;dockerfile&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-dockerfile line-numbers&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; python3.6 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;m pip install django&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;silk==4.2.0 django&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;toolbar==3.1.1 ipython pyflame git+https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/towavephone/django&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;toolbar&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;mongo.git@v0.3&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改启动脚本，挂载 tmp 目录&lt;/p&gt;
&lt;p&gt;deployment/local/start.sh&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61567896877797155000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`-v /tmp:/tmp`, `61567896877797155000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;-v /tmp:/tmp&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改本地配置&lt;/p&gt;
&lt;p&gt;local.json&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30157622234524650000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
  &amp;quot;debug&amp;quot;: true,
  &amp;quot;debug_tools&amp;quot;: [&amp;quot;django-debug-toolbar&amp;quot;]
}`, `30157622234524650000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;json&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;json&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;debug_tools&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;django-debug-toolbar&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 setting.py&lt;/p&gt;
&lt;p&gt;settings.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26446425115010118000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`DEBUG = __config.get(&apos;debug&apos;, False)
DEBUG_TOOLS = __config.get(&apos;debug_tools&apos;, [])

if DEBUG:
  if &apos;django-silk&apos; in DEBUG_TOOLS:
     SILKY_META = True
     SILKY_PYTHON_PROFILER = True
     SILKY_PYTHON_PROFILER_BINARY = True
     def request_func(request):
           # 过滤掉 debug toolbar 的请求
           return not (request.path.startswith(&apos;/__debug__&apos;) or request.path == &apos;/&apos;)
     SILKY_INTERCEPT_FUNC = request_func

     MIDDLEWARE += [
        &apos;silk.middleware.SilkyMiddleware&apos;,
        &apos;entry.middleware.SilkProfileAllViewsMiddleware&apos;
     ]

     INSTALLED_APPS += [
        &apos;silk&apos;
     ]

  if &apos;django-debug-toolbar&apos; in DEBUG_TOOLS:
     MIDDLEWARE += [
        &apos;debug_toolbar.middleware.DebugToolbarMiddleware&apos;
     ]

     INSTALLED_APPS += [
        &apos;debug_toolbar&apos;,
        &apos;pympler&apos;,
        &apos;debug_toolbar_mongo&apos;,
        &apos;pyflame&apos;
     ]

     INTERNAL_IPS = [
        &amp;quot;127.0.0.1&amp;quot;,
        &amp;quot;localhost&amp;quot;
     ]

     def custom_show_toolbar(request):
           # 路径为 /debug，即显示 silk 时候不需要显示 debug toolbar
           from debug_toolbar.middleware import show_toolbar
           return show_toolbar(request) and not request.path.startswith(&amp;quot;/debug&amp;quot;)

     DEBUG_TOOLBAR_CONFIG = {
        &apos;SHOW_TOOLBAR_CALLBACK&apos;: custom_show_toolbar,
     }

     DEBUG_TOOLBAR_PANELS =  [
        &apos;debug_toolbar.panels.history.HistoryPanel&apos;,
        &apos;debug_toolbar.panels.sql.SQLPanel&apos;,
        &apos;debug_toolbar_mongo.panel.MongoDebugPanel&apos;,
        &apos;debug_toolbar.panels.timer.TimerPanel&apos;,
        &apos;pympler.panels.MemoryPanel&apos; ,
        &apos;pyflame.djdt.panel.FlamegraphPanel&apos;,
        &apos;debug_toolbar.panels.versions.VersionsPanel&apos;,
        &apos;debug_toolbar.panels.settings.SettingsPanel&apos;,
        &apos;debug_toolbar.panels.headers.HeadersPanel&apos;,
        &apos;debug_toolbar.panels.request.RequestPanel&apos;,
        &apos;debug_toolbar.panels.staticfiles.StaticFilesPanel&apos;,
        &apos;debug_toolbar.panels.templates.TemplatesPanel&apos;,
        &apos;debug_toolbar.panels.cache.CachePanel&apos;,
        &apos;debug_toolbar.panels.signals.SignalsPanel&apos;,
        &apos;debug_toolbar.panels.logging.LoggingPanel&apos;,
        &apos;debug_toolbar.panels.redirects.RedirectsPanel&apos;,
        &apos;debug_toolbar.panels.profiling.ProfilingPanel&apos;,
     ]

     PYFLAME_CONFIG = {
           # https://gitlab.com/living180/pyflame
           # 默认值 None 告诉 pyflame 使用环境变量 PATH 搜索 Flamegraph.pl
           &apos;FLAMEGRAPH_SCRIPT_PATH&apos;: &apos;/root/third_party/flamegraph.pl&apos;,
     }

     # mongo toolbar 每个操作的最大显示数量
     DEBUG_TOOLBAR_MONGO_MAX_OPERATION_SIZE = 20

if DEBUG and &apos;django-silk&apos; in DEBUG_TOOLS:
  MEDIA_ROOT = &apos;/tmp&apos;`, `26446425115010118000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;DEBUG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; __config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;debug&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
DEBUG_TOOLS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; __config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;debug_tools&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; DEBUG&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django-silk&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; DEBUG_TOOLS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     SILKY_META &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
     SILKY_PYTHON_PROFILER &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
     SILKY_PYTHON_PROFILER_BINARY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;request_func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
           &lt;span class=&quot;token comment&quot;&gt;# 过滤掉 debug toolbar 的请求&lt;/span&gt;
           &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/__debug__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     SILKY_INTERCEPT_FUNC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request_func

     MIDDLEWARE &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;silk.middleware.SilkyMiddleware&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;entry.middleware.SilkProfileAllViewsMiddleware&apos;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

     INSTALLED_APPS &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;silk&apos;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django-debug-toolbar&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; DEBUG_TOOLS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     MIDDLEWARE &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.middleware.DebugToolbarMiddleware&apos;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

     INSTALLED_APPS &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;pympler&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar_mongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;pyflame&apos;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

     INTERNAL_IPS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;localhost&quot;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

     &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom_show_toolbar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
           &lt;span class=&quot;token comment&quot;&gt;# 路径为 /debug，即显示 silk 时候不需要显示 debug toolbar&lt;/span&gt;
           &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; debug_toolbar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;middleware &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; show_toolbar
           &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; show_toolbar&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/debug&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

     DEBUG_TOOLBAR_CONFIG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;SHOW_TOOLBAR_CALLBACK&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; custom_show_toolbar&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

     DEBUG_TOOLBAR_PANELS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.history.HistoryPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.sql.SQLPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar_mongo.panel.MongoDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.timer.TimerPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;pympler.panels.MemoryPanel&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;pyflame.djdt.panel.FlamegraphPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.versions.VersionsPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.settings.SettingsPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.headers.HeadersPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.request.RequestPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.staticfiles.StaticFilesPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.templates.TemplatesPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.cache.CachePanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.signals.SignalsPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.logging.LoggingPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.redirects.RedirectsPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;debug_toolbar.panels.profiling.ProfilingPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

     PYFLAME_CONFIG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;token comment&quot;&gt;# https://gitlab.com/living180/pyflame&lt;/span&gt;
           &lt;span class=&quot;token comment&quot;&gt;# 默认值 None 告诉 pyflame 使用环境变量 PATH 搜索 Flamegraph.pl&lt;/span&gt;
           &lt;span class=&quot;token string&quot;&gt;&apos;FLAMEGRAPH_SCRIPT_PATH&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/root/third_party/flamegraph.pl&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

     &lt;span class=&quot;token comment&quot;&gt;# mongo toolbar 每个操作的最大显示数量&lt;/span&gt;
     DEBUG_TOOLBAR_MONGO_MAX_OPERATION_SIZE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; DEBUG &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django-silk&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; DEBUG_TOOLS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  MEDIA_ROOT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/tmp&apos;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改中间件以便让 silk 测量每个接口的性能&lt;/p&gt;
&lt;p&gt;middleware.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32523095773162303000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class SilkProfileAllViewsMiddleware:
     def __init__(self, get_response):
        self.get_response = get_response

     def __call__(self, request):
        response = self.get_response(request)
        return response

     def process_view(self, request, view_func, view_args, view_kwargs):
        from silk.profiling.profiler import silk_profile
        return silk_profile(name=request.path)(view_func)(request, *view_args, **view_kwargs)`, `32523095773162303000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SilkProfileAllViewsMiddleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; get_response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_response

     &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__call__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response

     &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process_view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; view_func&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; view_args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; view_kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; silk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profiling&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profiler &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; silk_profile
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; silk_profile&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view_func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;view_args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;view_kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 urls.py，暴露调试地址&lt;/p&gt;
&lt;p&gt;urls.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5582151372340505000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`if settings.DEBUG:
  # https://github.com/jazzband/django-debug-toolbar/blob/3.1.1/docs/installation.rst
  # https://github.com/jazzband/django-debug-toolbar/blob/3.1.1/docs/configuration.rst
  if &apos;django-debug-toolbar&apos; in settings.DEBUG_TOOLS:
     import debug_toolbar
     urlpatterns += [
           re_path(r&apos;^__debug__/&apos;, include(debug_toolbar.urls)),
     ]

  # https://github.com/jazzband/django-silk/tree/4.2.0
  if &apos;django-silk&apos; in settings.DEBUG_TOOLS:
     urlpatterns += [
           re_path(r&apos;^debug/&apos;, include(&apos;silk.urls&apos;, namespace=&apos;silk&apos;))
     ]
else:
  urlpatterns += [
     re_path(r&apos;&apos;, root_router)
  ]`, `5582151372340505000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# https://github.com/jazzband/django-debug-toolbar/blob/3.1.1/docs/installation.rst&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# https://github.com/jazzband/django-debug-toolbar/blob/3.1.1/docs/configuration.rst&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django-debug-toolbar&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG_TOOLS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; debug_toolbar
     urlpatterns &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
           re_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;^__debug__/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; include&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;debug_toolbar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# https://github.com/jazzband/django-silk/tree/4.2.0&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;django-silk&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG_TOOLS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     urlpatterns &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
           re_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;^debug/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; include&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;silk.urls&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; namespace&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;silk&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  urlpatterns &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
     re_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; root_router&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;vscode-debugger&quot;&gt;&lt;a href=&quot;#vscode-debugger&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;vscode debugger&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装依赖库&lt;/p&gt;
&lt;p&gt;Dockerfile-local&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61079551421452760000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`RUN python3.6 -m pip install debugpy==1.5.1`, `61079551421452760000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;dockerfile&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;dockerfile&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-dockerfile line-numbers&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; python3.6 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;m pip install debugpy==1.5.1&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;首次启动时打开调试端口，local.json 及 setting.py 部分省略，不设置 &lt;code class=&quot;language-text&quot;&gt;DEBUG_PORT&lt;/code&gt; 默认为 9999&lt;/p&gt;
&lt;p&gt;manage.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;81263885233563420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`from django.conf import settings

if settings.DEBUG:
  # 这里只在第一次启动，防止热重载中断 debug
  if os.environ.get(&amp;quot;RUN_MAIN&amp;quot;) != &amp;quot;true&amp;quot;:
     import debugpy

     debugpy.listen((&amp;quot;0.0.0.0&amp;quot;, settings.DEBUG_PORT))
     # debugpy.wait_for_client()
     print(f&amp;quot;Django debug started, port is {settings.DEBUG_PORT}&amp;quot;)`, `81263885233563420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conf &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; settings

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# 这里只在第一次启动，防止热重载中断 debug&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;RUN_MAIN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; debugpy

     debugpy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;listen&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG_PORT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# debugpy.wait_for_client()&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Django debug started, port is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG_PORT&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置 vscode 调试面板&lt;/p&gt;
&lt;p&gt;.vscode/launch.json&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;91516050870759500000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
  &amp;quot;version&amp;quot;: &amp;quot;0.2.0&amp;quot;,
  &amp;quot;configurations&amp;quot;: [
     {
        &amp;quot;name&amp;quot;: &amp;quot;Run Django (Docker)&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;python&amp;quot;,
        &amp;quot;request&amp;quot;: &amp;quot;attach&amp;quot;,
        &amp;quot;pathMappings&amp;quot;: [
           {
              &amp;quot;localRoot&amp;quot;: &amp;quot;\${workspaceFolder}/后端代码位置/entry&amp;quot;,
              &amp;quot;remoteRoot&amp;quot;: &amp;quot;/root/entry&amp;quot;
           }
        ],
        &amp;quot;port&amp;quot;: 9999,
        &amp;quot;host&amp;quot;: &amp;quot;127.0.0.1&amp;quot;
     }
  ]
}`, `91516050870759500000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;json&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;json&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;configurations&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Run Django (Docker)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;python&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;attach&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;pathMappings&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token property&quot;&gt;&quot;localRoot&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${workspaceFolder}/后端代码位置/entry&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token property&quot;&gt;&quot;remoteRoot&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/root/entry&quot;&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;port&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9999&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;host&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;疑难点&quot;&gt;&lt;a href=&quot;#%E7%96%91%E9%9A%BE%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;疑难点&lt;/h3&gt;
&lt;h4 id=&quot;django-debug-toolbar&quot;&gt;&lt;a href=&quot;#django-debug-toolbar&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django-debug-toolbar&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;需要找到适配 Django 3.1 以及 python 3.6 版本的 django-debug-toolbar&lt;/li&gt;
&lt;li&gt;为了显示 Python 代码火焰图，需要合适的 pyflame 配置，需要在 docker 里面将 &lt;a href=&quot;https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;flamegraph.pl&lt;/a&gt; 暴露给 &lt;code class=&quot;language-text&quot;&gt;/root/third_party/flamegraph.pl&lt;/code&gt; 这个路径，否则不能正常工作&lt;/li&gt;
&lt;li&gt;为了同时支持显示 mongodb 的 sql 显示，这个 &lt;a href=&quot;https://github.com/hmarr/django-debug-toolbar-mongo/compare/master...towavephone:django-debug-toolbar-mongo:master&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;django-debug-toolbar-mongo&lt;/a&gt; 需要兼容旧版本的 django&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;django-silk&quot;&gt;&lt;a href=&quot;#django-silk&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django-silk&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;需要找到适配 Django 3.1 以及 python 3.6 版本的 django-silk&lt;/li&gt;
&lt;li&gt;挂载 tmp 目录以便让性能分析结果持久化&lt;/li&gt;
&lt;li&gt;编写中间件以便让每个接口都能进行性能分析&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;效果-1&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果&lt;/h3&gt;
&lt;h4 id=&quot;django-debug-toolbar-1&quot;&gt;&lt;a href=&quot;#django-debug-toolbar-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django-debug-toolbar&lt;/h4&gt;
&lt;p&gt;本地后端启动后，直接访问后端地址，即可看到以下常用功能：请求历史、请求涉及的 django orm sql 语句执行情况、请求涉及的 mongo sql 语句执行情况，执行时间分布、内存占用、内存调用栈&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-68585.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.29225736095965%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA8klEQVQoz52NTUvDQBBA928IcZNWPBRs2NSbF3vxD/oLApqUInr0WPDkyepZEGujoc3OztrVQLr5app6KEUP5vFmmMtjSDS/Cz9G3+o+Sx+ydJxnj0X+9JepHpfl8+3Neadt9Z0uCcPX4P0ligLEmVI8jjFJVOVyvRc7xrEsy/zac/fofp85ZDJ5m06D2TwSKAEQQAix3pWIUqmvbeXnosiLq4tLetA+ZT2CNfAbQgCi2BaA62UydF2r1TrrHf/EG6SU1WwOWR87AOda66HnHVrmicMI/gdex/5gQCntHHUbxb5vmqZt2w1jwzCax9VnxtgKS8HFnboIPxoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 14 48 20&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-a67b7.png 200w,
/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-0b187.png 400w,
/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-fee1c.png 800w,
/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-b1a91.png 1200w,
/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-95179.png 1600w,
/static/2024-05-17-14-48-20-0adf0a5ebf3281c74f48215c11d0a066-68585.png 1834w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-01d9c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40.022296544035676%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABcUlEQVQoz52Qy07CQBSG+y6ICFourSxKDFoawYStG30Jg4Etr+CCp/ElUFFaEmiB1lAIvUADVBD4nZkFe1l8+WfOmfkyZzjLfIVJ2G3fsP1tstzv3hl0zerbJttj/8GSntvvvlGvPyGbFSAIPBKJU8iKDM6yDLjuGD9hgJCwWPhkb7MaXVOWyxl8bwLHsRm0t9ks0Wi8oFQqoFwuQlGucf/wCE5TNRjGAP3+ALpuoNfT8dVWMRia5KIHz/NZ0l6324NBzrVan7DtMXlhHckkD1EUwPMXKJTK4HRdh6qp0DQN7XYbnU4Hw+EAo9EIlmmS7zCJqAvLsojEZvT7BiaTCSqVZzJqAplMBpHICaR8AdxsNkMQBJjP54ekNd/3D9AaTc/zDqzXa1SrVaRSKUiSRIQR5G5uwbmui/8ynU6xWq2YMBqN4py8MhaL4UouHi8Mw5AJ6cjpdBpn8Tjyyt1xQsdxmLBWq7H/uxRFxIkwl5fxB6GQCAohws7rAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 14 54 57&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-a67b7.png 200w,
/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-0b187.png 400w,
/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-fee1c.png 800w,
/static/2024-05-17-14-54-57-7a03987c785c823c6bc2e0be7908e390-01d9c.png 897w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-01d9c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.71460423634336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACRUlEQVQoz21RyW7aUBT1j1RKaISaqElQEhNV2SSkmQR400hVFlWj/kE+oVJ/pOqqw7qVWvUHUIAMDnMAgw3G4AFsjMuQ0/teRdVFF0dX7737znCvoKnf0VC+QaXa0X9ysHNb+4HZJE24xMMsw4GHLCGD2fQW/X4Jp6fHWF9fxcpKGEtLC4g934dQreWgNktoNIowTRWWpaHXa8K0NTiBhdF0gMFA57DMJr0pvMc0NSQSR1hdXcbykyUsLD7C7uExhLtcHmbPgmF0SdWFY/fh+QHuU5f4+vYdbj9+xojO48kU7ZaOSqWKUqkCm/rOz19je1vEzs4ziOIWpBcvIVxfX6FWr6HV0tBsNKBp5NA04RgGjHIZRvWe3PUxcAcYDofwPI/EDTiOg4uLC5wcnyAejyMW28fZqzcQKpUKZFkm1RIYeTqdRrfbxSgI4I/HCMYT4NeM8EBOR/CGHsZ0zwgZUTQaRVQUEQo9xt5hAkImm0GxWESZ3CiKgnsSaKoq/xAQgW518CH9Be/Tn1DTFdimhXa7Dcu2kUwmaSErNMenRBjC7kEcgnwnc4JOp0OxW1QNGroFl6L5Qx/OwIGs5XGr5mD2LXiuC13Xec+ccG1tjdeDE4m2XK0in89jHv365oY22fs7L1YnwQRTAhNgdwGNgyWQJAkbGxucMBwOI3aUhJBKpVAoFPhC6vU6j66TW9//85nB9VyO+ZmJMELmMBKJ8Dkywr1Dinx1leUOWQSb5sLAmucO/4d/CUVayNbWJhYXQ9iXzvAbmHSBNuk/AV0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 14 56 02&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-a67b7.png 200w,
/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-0b187.png 400w,
/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-fee1c.png 800w,
/static/2024-05-17-14-56-02-e5a7140eb7247d296af1a073d6767cfd-01d9c.png 897w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-8afa6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 100.6674082313682%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAADoElEQVQ4y11Uy1LrRhT0t1zwQ1h+YLxIbliySVGV1c2fECCmKEiRVbLnV5LssssqyYVbXEhhDLItWZYs6+kX2HROjxCh4qoujcYzPX26zyjnOr/DMH6D8fALuur5K+LoD+D5I1bLv/C8+vt/+Ijl058ATPz8Uwu1mg5dL6FYXEOprCHXbl+h07lBEDjwvAHGY8JGFLkIQ0fBlznft1/heRbmswgnJy1sbBRQqWhC+A5arY7czc0/+Pz5Gvf3D6K0C6Pbk00B7u46eHgw0OsPMJnM5IAJojhFGMVYrZ7RarWwtvYOpVIRxUIehXINOdd1RNFYlIRIkgTD4RDeaATXcRDHMQLZnEynmMzmCokgjBPwd3Z2hmazie3tbTQaDdSbXyLneR5c10Xg+4o4iiI5faU28DmdTtDv90XlBI+PCyyXS1gDS1RGOD8/x+7uLr798AE7OzvY+fob5EZUI4Rdw5CSDQwGA3S7XdiidDabgQdyngfNFwshfYTpDOCOR6rkcrkswdRSUCFLJKktRLZtK8KrqytcXl7i06dLXF/fwDRNVf7T05NSaDsSjD/G4eEhNE1DvV5XxBu1JnL0jqDKkStqSW4PVfm+2JCB7/R4Pp9jYFny7ovCI2mZivKPhCV9EzkuDIJAqVR+ypOesVwqonecZ8lcR1BhlMSqZF3X8dX79ypprf5F6iFhSVmWnEy0222laCF+RVEIRxKnssWLh/1eD6YEc3BwIN7VReEm8vk8SpVGmrLnjZEpVW0jc1nikXj3FrG0TNvowLRM8fB7VKtVFUiBhNVm2oe+EMXiD43PyOgby42StAeDKFGI5Z1p83d0dKT8I/L5dWibL32YljNXRNOptMrYU4RMlCUSoRzK4Djmej8M8N3+fkq42UhLpkL6xwC4iGOzbyoPWXp6wFQRk4ztw47gWjfysCeEFUm52dxKCfU6+9BWhI9SBkm6cp95j29vb3FxcYHO/b3c8w56EgR7NFMYJCH2Dw/kw1DB1tYL4UbtTckCjtmDBDczTRLd3YliSZo2MCimzZ7c29tThLzPrx5yUeYTF7NMElMtkSQTFRZL5X9Z+pw7Pj5WfcibUigU0rbhgqzHSE6vCEvagoT0jw1OAhLRU46TaSJfmx9fCYvF4n83JWvYMIxUT7KRScgQJkJAQpJRJcdcz6/T6ekPipBJ63oZWnUrLZl+ENzAdza4/+Zzxv8ywkypPRri5PRUqVM3ZX0dRfnA/gvbnRwJ6ftU7AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 14 56 44&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-a67b7.png 200w,
/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-0b187.png 400w,
/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-fee1c.png 800w,
/static/2024-05-17-14-56-44-069f0e53309c41e0954d5dd6ad629634-8afa6.png 899w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-6cf8d.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 104.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsSAAALEgHS3X78AAAD90lEQVQ4y42V3U+bVRzH+T82BYSuZvQVytAbk+2CK5dsXQe0OMlivHBR0BmXGDWLmYnGGa8wU5d4oe5NY3Q2YgI4zTKzMQkgMqAMaOnT9+ec0xYoJm769XtOyyTaCy++Oc3ze87n+Z7fy2lDyhpFPD6CdGoMf/05/b90749p3L9vYWDgCLxeF9ranGhq2o1QTwgNUqZgF5IQIoVSKUtlqmuRazmHYnIWJZnks1wtloFSafy+VUQ4fBRte51VYPNDONwbRoNSNl8Q0KuUhX8kqKKEiN2GzCUh+c52zLbzqGyuExiGw9FK4F4Cm3G4p187zGFb6t8q8iOLN6Bycf4umGfb7xZVAaFQiEf2ot3vR3PzIzh0NKKBWb6YRd21RODsKGTmbg24M55DMBhER0cH9u3rxMONjTikHSrJnFDyP2vaANXst5CpxRpwZzxrgE6nzmEbWlpaEew9poHcWEe6WMbhzUtQ6XnmM1d7vh3PGKDb5TbHbnU4EOwzQG6sJ2FBlVmE2xch12bpUDuzqh+iNLi3tweBQMDI6XyUDp/SQAv1xcqW8ij9+imUNcUqZ6rPanENHhwcRHd3Nw4efBJdjz2O48+d1MAk6kmKBNR6HvboOcjlWzy+Bq4RpHs2gY2NvGkbXRSPx4PGpib2oTnyGupKxA1QjA9DrvwMWViBsldNTNhxbFVsRCIR+Lw+uFyunUA6qSMpuHkzj/wX70Is/AS5egvSmmEu6ZDgCmOR/gg6OzvhcbsJZGNHntFAOqknQUfrbODrZ6Fi45CxH6ESPLrSDpdRqVSBgY4Aj+xmH2qHT2ugPkYdiWWoSg6FK29CTH8HuXSNubxOYJzAuwTmTA49Hi/8Pl8VGIpoIJ3Uk+B0bHAqxs5AzUdRuBmFmBsjcKUGzBqgjzCfz1udlCN9GsiNRstmlWLJSBU4HVtZFC68CvvGRagFupwbMR8SjG9VMujr62NT++D3a4cPcqhhS1UVV1DcsIxUmcXZZKt8/xrUzCXO9FXIma8JjBEYY1HSpspuFuRBUXSVpb3AluBo2YsQqUlkuSmrN09/CZWfRz56GuLyC7B/+BBi6iu2zgKBiwSmCAwT6KFLTxWoL4dSJYHyvSzKKCEzE8VEvwOTx5xIDwUgExNIR99C6swTEFdfh5y8QOAcgQvMoWWA5vpqb4djz57qbZP85gPMv3cSyXNvwDp7HL+caMFvg60Qp/0oZycQ/+hlxF9xwf78eYhrw7wbpwi8wxyumVnWRdEX7K5duznLBN658jYm3ulHbPhZxM+fwNT5FzH3yRCsy6dgrU4iOf4Z4u+/hFz0Y+ZxhOnROVxFuZzmf8oAurq6sP/AATOCQ5ztvwGd9SVBe/Z4VgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 14 59 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-a67b7.png 200w,
/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-0b187.png 400w,
/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-fee1c.png 800w,
/static/2024-05-17-14-59-42-1eeb4ab95edd9c44c4dc51f728e4d06d-6cf8d.png 900w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;django-silk-1&quot;&gt;&lt;a href=&quot;#django-silk-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django-silk&lt;/h4&gt;
&lt;p&gt;本地后端启动后，访问 &lt;code class=&quot;language-text&quot;&gt;后端地址 + /debug/&lt;/code&gt;，即可看到以下常用功能：请求概况、请求耗时、调用栈可视化、清空数据库（此功能涉及到读写 local 环境的 pg 库，如无必要请勿开启）&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-25bb8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 75.82116788321169%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAABzElEQVQ4y41T207CUBDkE0GNCUExMdFf8NGPJDVCoQUKtKW3c6G8rjtbSilU48NmN+ecne7MTnthGNLHxyfdP7zQaPROw+EbPT6+0mAwpn5/xPlZot9/uojRucbd3d34FM/Us9ZSUeSU59k5siwjpRQFQUCu69J2u6E4jmi/33OOJao6ojRNuL/gvlyiZ4yhw8G2wlqcHRg4pdlsJgA4x4fQrFQhWWtNZXkgDIXAGwE0RvGlkgfGVIEHeZ7SbrejMNzxtGsBxkRJEnMkXKdU9dd9qgbUN4EJAeS6M/KXHk2n3xQxxbIs+Y4Z2IpJ01PVJ8DLaB4AFBnT+v6CnC9HdMJ0ihk175v+3jWIANSZJ9B8rljL7WpJvrcgc9Kwkac9TAdgo2GWJUIb21wx7aXv0XYT8EKyK7o3GppODaMoosViznQ9yav1iqa8dSwGG71mdkHZdIxvzhbCIgDoOI7U8Cp8+k8Nm9pazqDGxq81zHk6+FOLLOY3yre2qX0Iq8RxSMG6AtS8ENim/d502aY9ITTEMvDrITwGmzPtvCjOdvpDQ93hRSOTgNbxeBSDTyYTsYtQ1jXl9rS/UsY5fscC/y3rVmuIGlvWYuxbyj+40E9YI6spbwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 02 21&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-a67b7.png 200w,
/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-0b187.png 400w,
/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-fee1c.png 800w,
/static/2024-05-17-15-02-21-4165c850d0ab69904b7b061565ed3a6f-25bb8.png 1096w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-85a77.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.094202898550723%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAAwklEQVQY04XOywqCQBQG4HmwNkEXI4roYkUP1K5lr1YLJ1PLRsdMR2da/p0uBEHZ4oNz/sOBn62XK/RqHSy6U8ytCYb1PmaNAVqtMZrNapZlo922aR69MyZ9HyHf4bRzH0LOISgTUQwhop8iujsOh+vuEceS9mfOSqNhrldoYl60MdBa/6QKhUuaYrvZgFOBJEmQZRkM/TGlFFSef7pnFXJyOQkcPA/HIEB2PiN//bF/z98VKKillBIpNS3LkvbicbsBPYpVWYRoHggAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 03 45&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-a67b7.png 200w,
/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-0b187.png 400w,
/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-fee1c.png 800w,
/static/2024-05-17-15-03-45-daa5e9860d381667bcf7fb0bd0615c92-85a77.png 1104w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-a5624.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 84.22131147540983%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAACZ0lEQVQ4y3VU246bQAzl/z+kj/2D9mnVp0pRErXa3DZAuATC/RICZDi1PcAmu9uRjjzxzBj7HDtGUWTIixzFEwqxYeDD9zycTjYsy8T57MO2LURRiKoqkOfP99kaZVlgAl9iqy8USNMESRwhtE3sdlt4FDxJEmRZ+iGBfH5j8Ff4kkYsQcqyFFRVibquKEAi2fGDqqrEP93RKMRKQMexsFgssFqvsFwusV6v4fs+LpeQEBAuso8iyjQMENJvLln2YShn0z6OYxhVleN6rSmTEk1zJdRib7cr+XWGnBH7uq4ldKNt0Y6W0fcd2rblkjNsNhu8bl5xcmwRIAgCKU2pO4ZBzRYYyA5iNfCwH3C/37UoJil4tCy4vgeLVHRcF83ths9rwNdL+3VAUtY1TXj7PcztFm/7HfaUsU0fsKwjtclJWoU5YtG0eJHsmbM4ft+zMAZzlZGyHNTcbBE4Di6Oi5QuJGksvcZtwpUwT33fj7ajjHqB9vXCr1GTIA3LTioG1BrXusb/12feAPVcctM0qLm34gT5JUJLacuTQQuglBJBJlGmgPpczXYOyCV/XBxEB7o/BWS19dldzt6D6sBMg+F5DjXzSmY1Yd5kWlLhjScky7JxPyEb/YlM1SOYbyMIzjgcDjDNI7XLSbqep8O2bfEdj2/yMZvm2XUdaiv26z8K7lc9JXpS+GMGdznXPqknE9BqdFRepwaaCJoCOrsR3zfqT4acP0wNg/0Gc/Go2kw08dQVCbyXnzh8/4b+Ws9ifTUpk4iGUv1I+Dv5dARF/ZXu/uL8+xfclx9Itn/081Gs6Y16EIXxDzhMGaPpeLzfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 03 00&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-a67b7.png 200w,
/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-0b187.png 400w,
/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-fee1c.png 800w,
/static/2024-05-17-15-03-00-e45e1cf0aef8302008912357ef3a95e2-a5624.png 976w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-5cc8c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 29.758011772400263%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABHklEQVQY02WRy26DMBBF+eU86CZpm00/Jat+SKSuYEF5pRISEAWCwLwxD916Js2i7eJoPLbvnRlb2+3esF4/43h8x+n0gdVqr/I97223r9hsXjjq+uHO04Hzv+j6HW0YBkg5oOs6tG2LaRpRlgJFkaOua0aqO8PPnaZpMM8zlmVRzAzlUkpGK8sSRFVVjBAl+r5Hnue4XhOORVHgdsuQpqk6Fywcx5GZponNG9UM+WgPowe0SQIhCkRRhCRJVAzhuh4sy1LRhWma8DwX5/MXgiBAFMdcjPS/DGk8MqSqtA7DEKkyjJWx5/mwbRuO48IwDLX+ZHPHceD7Zy5Omn8dEjRylmWILxfuMFYdeL7PBmRKMVLF6M1pGvoHelvSfgPFt6rw9cRuPQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 04 41&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-a67b7.png 200w,
/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-0b187.png 400w,
/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-fee1c.png 800w,
/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-b1a91.png 1200w,
/static/2024-05-17-15-04-41-883c55b6ac2367ad3e69554a66f027bb-5cc8c.png 1529w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;vscode-debugger-1&quot;&gt;&lt;a href=&quot;#vscode-debugger-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;vscode debugger&lt;/h4&gt;
&lt;p&gt;启动 vscode 调试，鼠标设置断点后，访问某个接口即可触发调试过程&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-04-15-14-05-01-60b2ac02b5fc54da1a00ceba69325ec3-d6910.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 568px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 91.90140845070421%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsSAAALEgHS3X78AAABLElEQVQ4y8XTyW6DMBAGYN6kAi/BhKVKoHZsYwNCqUKwGiWVuhyqtuq573/sBKKox8Kl6Nec+BjZw3hKq41SSRwXRSGESJKEMRZFUcjCvsife/fe7T77PeSj3389uKe2edXKrlaUMU/U23j/FkfMGJtlt5QuMCYQhPAuy3bGHow51fVj0xyrCuqW88N6LZfxTYA8qUtRVkmaAgYAjBAKwYQyQmOEI4QZJEDRWDFZEkrhBUy8tqruSw3M99HIrgGPhvo7aAgZ8YsxByUlY8HwPfLnnPH36eja1oahj6fjTkon7tLFAk2RF7zRmisVIEJmYME5ZKq84LqutFLXCU3DnHP4t2Z3Pj8w5zkY2uZ5PrOz1lpK+R9nttaq2bcNO8xnz9k513Wd7wewyVPxD9dMsOJth/sWAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 04 15 14 05 01&quot; title=&quot;&quot; data-src=&quot;/static/2025-04-15-14-05-01-60b2ac02b5fc54da1a00ceba69325ec3-d6910.png&quot; data-srcset=&quot;/static/2025-04-15-14-05-01-60b2ac02b5fc54da1a00ceba69325ec3-487ad.png 200w,
/static/2025-04-15-14-05-01-60b2ac02b5fc54da1a00ceba69325ec3-1cefb.png 400w,
/static/2025-04-15-14-05-01-60b2ac02b5fc54da1a00ceba69325ec3-d6910.png 568w&quot; data-sizes=&quot;(max-width: 568px) 100vw, 568px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-04-15-14-10-27-cf012a36086343963aabad0e53f2b9f1-a36d1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 604px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 20.52980132450331%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAA5UlEQVQY00WPyW6DQBBE+Q2zDfYYGGyG1ZhV2BDnlEQ55f8/5WVMouTwVN2lVqvKEsGeMFaEUcRR7kkSo0eJlAfiODSewLZtg/PHbvezO8Z37B2u6+J53oalpWQ6n6mUItI5w21kXTrWtef9beH1pWXsS+paU5ap0Yy2rbjUKef6Stp0qCRG+D5CCKwPFfOlNZ+ZRniC5JQydB1FnjGOA9drw3KfmMaWeZ5Y1huPx8LdeO0w0s93Lk1DYyiLHOtgKld5QWmeuq6DPEjSKEWFCq0zgiD4rePj+2Ljf/ZMMm9L97x78g33jXeZo2R9uQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 04 15 14 10 27&quot; title=&quot;&quot; data-src=&quot;/static/2025-04-15-14-10-27-cf012a36086343963aabad0e53f2b9f1-a36d1.png&quot; data-srcset=&quot;/static/2025-04-15-14-10-27-cf012a36086343963aabad0e53f2b9f1-765e7.png 200w,
/static/2025-04-15-14-10-27-cf012a36086343963aabad0e53f2b9f1-c9f48.png 400w,
/static/2025-04-15-14-10-27-cf012a36086343963aabad0e53f2b9f1-a36d1.png 604w&quot; data-sizes=&quot;(max-width: 604px) 100vw, 604px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-e4a77.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 60.4270462633452%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAAB7klEQVQoz41S7W7cIBD0a6S5j9g+MAaMDRjb50suqlL1/d9oOtDej1ZV2h+jBbTMzs5u5dOKYXBomhZ2GDDPc7lLKaCVhGgbCCGhrYOxFmEMCC7AGAtrh4LHue97VGm7wep8Meg6RaIOSvWFpGNMKSDNIwbb82OPZUzYlx1unAqRNqYI0NqQQ5Nw2bCEFXGc+cgEnSs6dFJBSIklBsxxJHlXVCfmLX6BJVkunFXlmNF1HaoQArz1SM5jcQZpoFISZQukUtivK+73Het+Y9sW67jgdb7C2QehpkpbomJ+5X1AHCJufsVbGPEeJ34aoFlNCIGJ7fhppEcWgh0kN+M1zFj5tnmCMZ8NCbNNVR6CNx43tv1tifi+zgjWFLLeKEyToQ0CbfOCy6UuCt/mHZGd/UQsMQuz/FfFGJFigusHElmqpcnZl07SswaTUzRdwWjJKSukiR6GpQzlMeW8HZad5KFV00Tv1g291mjaC1rKTsuKwEJPT19wPJ5wOBzx/Hwo8Xw+F5xOGaffkHMrR8LBRxg3oZU9ZG8xUnFPow+Hw18+HT9FNVBuXbf0RxRTM/K5rmucmPAn4ecgYW4tQyldiC6/SOu6KS1mlf+Fh8LrtuHj6wfu+5Urwn3idC33SXOJJSerO1HO/4KoX9C+nPAD0jxjZFiNl8cAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 04 15 14 13 23&quot; title=&quot;&quot; data-src=&quot;/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-fee1c.png&quot; data-srcset=&quot;/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-a67b7.png 200w,
/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-0b187.png 400w,
/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-fee1c.png 800w,
/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-b1a91.png 1200w,
/static/2025-04-15-14-13-23-81b5fedc0528a80f14c9a2934f01655e-e4a77.png 1405w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;flask&quot;&gt;&lt;a href=&quot;#flask&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;flask&lt;/h2&gt;
&lt;p&gt;针对 flask 框架同样需要上面的一套后端调试工具，由于这里使用的是最近几个版本（2024-05-17 15:12:42）的 flask（2.3.3），所以不需要考虑调试工具的版本，直接使用最新版本&lt;/p&gt;
&lt;h3 id=&quot;实现-2&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装依赖&lt;/p&gt;
&lt;p&gt;requirements.txt&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;12221837187829277000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`flask-debugtoolbar
flask_profiler
sqlalchemy`, `12221837187829277000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;flask&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;debugtoolbar
flask_profiler
sqlalchemy&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新主入口文件&lt;/p&gt;
&lt;p&gt;app.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;85232348510468400000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import flask_profiler

from flask_debugtoolbar import DebugToolbarExtension

if app.debug:
  app.config[&apos;SECRET_KEY&apos;] = &apos;123456&apos;
  app.config[&apos;DEBUG_TB_TEMPLATE_EDITOR_ENABLED&apos;] = True

  app.config[&apos;DEBUG_TB_PANELS&apos;] = (
     &apos;flask_debugtoolbar.panels.versions.VersionDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.timer.TimerDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.headers.HeaderDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.template.TemplateDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel&apos;,
     # &apos;flask_debugtoolbar.panels.logger.LoggingPanel&apos;,
     &apos;flask_debugtoolbar.panels.route_list.RouteListDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.profiler.ProfilerDebugPanel&apos;,
     &apos;flask_debugtoolbar.panels.g.GDebugPanel&apos;
  )
  # http://localhost:5001/
  DebugToolbarExtension(app)
  app.config[&amp;quot;flask_profiler&amp;quot;] = {
     &amp;quot;enabled&amp;quot;: True,
     &amp;quot;storage&amp;quot;: {
           &amp;quot;engine&amp;quot;: &amp;quot;sqlalchemy&amp;quot;,
           &amp;quot;db_url&amp;quot;: &amp;quot;sqlite:///flask_profiler.sql&amp;quot;
     },
     &amp;quot;ignore&amp;quot;: [
           &amp;quot;^/static/.*&amp;quot;,
           &amp;quot;^/_debug_toolbar/.*&amp;quot;
     ]
  }

# http://localhost:5001/flask-profiler/
if app.debug:
  flask_profiler.init_app(app)`, `85232348510468400000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; flask_profiler

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; flask_debugtoolbar &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; DebugToolbarExtension

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SECRET_KEY&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;123456&apos;&lt;/span&gt;
  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;DEBUG_TB_TEMPLATE_EDITOR_ENABLED&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;DEBUG_TB_PANELS&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.versions.VersionDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.timer.TimerDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.headers.HeaderDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.template.TemplateDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;# &apos;flask_debugtoolbar.panels.logger.LoggingPanel&apos;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.route_list.RouteListDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.profiler.ProfilerDebugPanel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&apos;flask_debugtoolbar.panels.g.GDebugPanel&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# http://localhost:5001/&lt;/span&gt;
  DebugToolbarExtension&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;flask_profiler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;storage&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;token string&quot;&gt;&quot;engine&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sqlalchemy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string&quot;&gt;&quot;db_url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sqlite:///flask_profiler.sql&quot;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token string&quot;&gt;&quot;ignore&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
           &lt;span class=&quot;token string&quot;&gt;&quot;^/static/.*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string&quot;&gt;&quot;^/_debug_toolbar/.*&quot;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# http://localhost:5001/flask-profiler/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  flask_profiler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;init_app&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;疑难点-1&quot;&gt;&lt;a href=&quot;#%E7%96%91%E9%9A%BE%E7%82%B9-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;疑难点&lt;/h3&gt;
&lt;h4 id=&quot;flask-debugtoolbar&quot;&gt;&lt;a href=&quot;#flask-debugtoolbar&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;flask-debugtoolbar&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;LoggingPanel 会影响控制台日志的打印，需要屏蔽掉&lt;/li&gt;
&lt;li&gt;这里没有 history，不能看到历史请求，待预研&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;flask_profiler&quot;&gt;&lt;a href=&quot;#flask_profiler&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;flask_profiler&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;需要正确配置 sqlalchemy&lt;/li&gt;
&lt;li&gt;忽略 flask-debugtoolbar 插件请求&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;效果-2&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果&lt;/h3&gt;
&lt;h4 id=&quot;flask-debugtoolbar-1&quot;&gt;&lt;a href=&quot;#flask-debugtoolbar-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;flask-debugtoolbar&lt;/h4&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-8ec0b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 43.02767227346717%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAArUlEQVQoz53PTQ6CMBAF4J4E2AkNK1hZT2wLhIN4A6MumqgLiG2nPxpLKnIABV5eZvflZRDnB6NPzl6sPf8t6GMI97bdE7IjZIs4v3F+7XtpzEvr5+9KaUIIlLKyLIuiQEpKAOX9O8yI9368dV3neY4xRl3XOeeGYZiPq6rCU5AQwlq7EiulluKmaTZpirMMAYA2ZhFmjCVJ8sXiIcxCTCmNomhaVrACx3E8/vwBEHDcJR3mJ6AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 21 50&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-a67b7.png 200w,
/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-0b187.png 400w,
/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-fee1c.png 800w,
/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-b1a91.png 1200w,
/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-95179.png 1600w,
/static/2024-05-17-15-21-50-b193cd3c53076915bb65408f5edef0cc-8ec0b.png 1843w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;flask_profiler-1&quot;&gt;&lt;a href=&quot;#flask_profiler-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;flask_profiler&lt;/h4&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-d49a9.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.22645290581163%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACKklEQVQ4y4VSTWsUQRCdX+HV/+BHPCiIIGt+QvDkaRVElISchBAPnvTu3QWNQhA8KJocInjyKgQPWTbuTnZmdrPz0TPTPdPT3c/qNrtu0I0Db6q7uqr61evyllc7aK2/RWttC1cfvMKdZztYefoR1x++xjXa33i0hct3O39h6V4HF9sd3Fx9g8cvPmFl8x0utF/COzgMcNCP8HM4cfDDBMNRBj9KZxiEJzb6Y/tBjO5ghB9dH4d+RDF2P4bHWA5RFqgER1VxyLoGjDkFozW0aggKumncnn6oK4GCMQheopESeV7CUzXHcHyMOE4o2EBTgaZRVIfW2riaNV0SJwmyjCFJUwhRgdwO069RmmIYvO2vDS492cP6t01kkkFJhTTLXJAtavGvb3rmUFIesU/SHN6tjWOcu7+LK+9b+Hz0xV2bZMksaeF3cmZ4Dh31oWxeSgyfbwucX9vB7b02+uURjNSYxBMXbG/VVr8zIHv7UMMuGpLHFayVxO73PnoTf1Ykz3MwapuXJRiJXhSF8xUWtLa+ks6yJEYe+mDjCKKqkNqWzZy0ml5utqbb3cPMaXVKBjOfZ2hCqt8aGkWsKLmhcfjfQ0gaDduBoBGzlnPuRomxDHb80qyAFyQDV8y2cpZWUykskyAIqKhAGIbOjqKI/DUVJIYf9nvEGeQQszYXYVo4I32VY8YcGWtrYu809CONqpYkMqcBltRWsxD23CZyYmXXU2sHXRBDq+EvPtjJwM2EQz8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 23 36&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-a67b7.png 200w,
/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-0b187.png 400w,
/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-fee1c.png 800w,
/static/2024-05-17-15-23-36-98c7c312ff4ecf5d42905aec2000ca18-d49a9.png 998w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-3cc2a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.06047966631908%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABMklEQVQoz42Qz0rDQBDG95UEPXjsqZ68CeJdFLwoKHgsXnwIby1C9OJJEHwQ0Ytt1TaBJKYJcZvd7H7OTAqNPUiX/bF/5ttvZlZ1L+6wQ3TPA2weDdA5DdA5C3Bw/YS9q0c5b5/cUqyPLYr/4XiAjcM+di/v0bt5xn7vAeptOMX7V4LRJG2YMt/4iDKhObdj6VJLDIkx6UeTRHxUPsvh6hqAp+n+wS9X0frWGw8zn6MilHMeTE2mngJugV/gVnEOxhjSO1hr5Y6H1hpZNqMKs0QEaZqKaRt+3IYTVFUl2jlVE4YhiqKQPaO5wtcxt2wRx7FcclZOwEgFK6Z8V5alGEdRJLBxnudStXr5tPIXLFh3cHv871IV7dmM98ZYKICzNu1ZEq1D8+dOVn6rNbdc4YfWX5GrUNRPMq7iAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 24 15&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-a67b7.png 200w,
/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-0b187.png 400w,
/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-fee1c.png 800w,
/static/2024-05-17-15-24-15-878d602076fd399c4e78577c96d7a964-3cc2a.png 959w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-aa0f5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 61.754780652418454%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsSAAALEgHS3X78AAABT0lEQVQoz5WSy3KDMAxF+f+/a5aBEALEIQb8fsnYTtWm3bSZJtXIM1rozJWuXEEACCGlVEq5vRbldu8slda6HY7cCQduMUyDwSJEEE4xKyxY7TUz3ARrgzXB+Ohns+qgQ4Iq5tixfrWcyvnMyEVMs1qkU5izWTCpmi98ooKuhhlvldc+hi1tqF5teRslIepKPrFhGZGXTlpwZ3k5rN1qGWoig8CP+SsHHgW5FdJK7LiPp73BGmeWXrnoUs655N/LV8aYtm0XxSRoG93tP1Hh6/seebpShXqbRykTLSR46v8HPF2n3dvuUDfH6dSJgaHz0ccUv0/yJ0wI2Td13TUDOzesY07KoFNOuRS0E8/xcOEvmFJa7+tuPI2c1Gs7SHJW06QpusUcxwIHeQxHiEKJK5+FVpBw12CjDxug8nPDnHNSIxdlAAPJQtYhx/TSV30Hfi23XS5WhlQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 05 17 15 25 17&quot; title=&quot;&quot; data-src=&quot;/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-fee1c.png&quot; data-srcset=&quot;/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-a67b7.png 200w,
/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-0b187.png 400w,
/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-fee1c.png 800w,
/static/2024-05-17-15-25-17-a70eb4e92fa05d3859d671be27fa11ec-aa0f5.png 889w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;实现历史请求&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0%E5%8E%86%E5%8F%B2%E8%AF%B7%E6%B1%82&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现历史请求&lt;/h2&gt;
&lt;h3 id=&quot;实现-3&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/towavephone/flask-debugger-demo&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Flask 调试项目 Demo&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;疑难点-2&quot;&gt;&lt;a href=&quot;#%E7%96%91%E9%9A%BE%E7%82%B9-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;疑难点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;接口并发高的情况下 flask_profiler 不可用，因为底层是 sqlite 不支持高并发写，可能要迁移到别的数据库，也可以屏蔽此插件&lt;/li&gt;
&lt;li&gt;flask-debugtoolbar 只能查看请求下的 mongodb 数据库访问记录，针对 sqlalchemy 的访问记录还需另外实现&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;效果-3&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果&lt;/h3&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-2bcbc.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 23.897659227000545%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAAy0lEQVQY04WOwU7DMBBE8znEHJIzLf/MqQR+gAM3rhH0ggpR2rjpOkrSOn5sGooqVMFIT2t5xrOO8vyJ5fKZ1eoFcW84pWkU9/ovssshfPL4cMd8fstsdkNkrVBVW4pyy/tHxcY2rEtLUWwodZZrq0s62vag7L/nxHg/arG4J01TkiQhCmFQQ/CHvf7M6caBuq7puhaRHcPg9UngkiYPsiwjjuMj0aWgcw7vPSGEPxkzP4VXMebaTIXnoVEiQt/3nHun80m/C40xR74ArjxprX8MZ9wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 08 06 13 55 54&quot; title=&quot;&quot; data-src=&quot;/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-fee1c.png&quot; data-srcset=&quot;/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-a67b7.png 200w,
/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-0b187.png 400w,
/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-fee1c.png 800w,
/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-b1a91.png 1200w,
/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-95179.png 1600w,
/static/2025-08-06-13-55-54-f63bf619476cd474f61870bf72f8f534-2bcbc.png 1837w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-f2644.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 19.793926247288503%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAAsklEQVQY03WOzQqCQBhFfRkFX8GtIPgCPnD7VlGLICsEx34kccbxJ52TWq2yC4ePu/gux9ptV2w/JMmax2OPkjGyPKBUjK5OVCN6AaWOdG3C+bQhDENc18XK84KikFyvOULcKMuK+71ASo3WHX0Pz6dZpOsGpqTphSiK8DwPi5+Y0UjN99v/Ycx7MMsEvu9j2zZW27Y0TTNT1/VopUc7Ofd+0ptejVlkGN6DQgiCIMBxHF7BMyO5gN8l/AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 08 06 13 56 19&quot; title=&quot;&quot; data-src=&quot;/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-fee1c.png&quot; data-srcset=&quot;/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-a67b7.png 200w,
/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-0b187.png 400w,
/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-fee1c.png 800w,
/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-b1a91.png 1200w,
/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-95179.png 1600w,
/static/2025-08-06-13-56-19-13305695e13d33240b64d98f3a8145e6-f2644.png 1844w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[分布式服务入门学习]]></title><description><![CDATA[…]]></description><link>https://blog.towavephone.com/distributed-services-practice-learn/</link><guid isPermaLink="false">https://blog.towavephone.com/distributed-services-practice-learn/</guid><pubDate>Fri, 11 Oct 2024 18:39:07 GMT</pubDate><content:encoded>&lt;h1 id=&quot;分布式系统、单体系统区别&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E3%80%81%E5%8D%95%E4%BD%93%E7%B3%BB%E7%BB%9F%E5%8C%BA%E5%88%AB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式系统、单体系统区别&lt;/h1&gt;
&lt;p&gt;分布式系统是对单体系统的一种改进，但这种改进同样也带来了复杂度和实现难度。&lt;/p&gt;
&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h2&gt;
&lt;p&gt;单体系统是包含一个物理组件的系统，能够独立进行部署和运行。可以看到在单体系统内部可以采用分层的方式合理组织代码结构，但从物理上看就是一个能够独立运行的应用程序。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-11-17-35-01-308c2c05327bb9cd29e81867ac87924c-6de41.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 327px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 118.65443425076452%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsSAAALEgHS3X78AAAEmUlEQVQ4y3VVW1NaVxTmH/WhmUnVxFZF21jtj2hfOtMm5qUvferkpZNpp5k0o2JiFBC5Q7gq14gRFBA4gAQVRUhQ4yVj8BILcvm6zj4ErE73zDdrr7XX/vZae62zjyjORaHTjsBuew6rZbwJm3UcM/YJsrfA6zxs13yfw2iQwO+fg0irHQXwmrBOWLuENKqVKFDnaJ4gcIJe4/WVK/7rzN/tmoSIj4w3lP4Jk4w3kETpPIQ571NEwtPwz0+Ai6qxHFLC45Zgu+BqBCH4Vy4ibE8yYYbIankmnFJP4OD9Enb3Fgh+7BCyuZfYyHqR2fRiLeNGPGlFImUn3YO9/QDe7fvJN4BqNc6ijkWNEJnNT0nJg4soYBnpx7z0W/gIfnk/AooBhsXpASwpB0l+xxBQDDIfn3QATkkv5mYesixjEQMRmiSkbMOqf4BjdxtlcgeVgBj7ji4UvV0484lx4OjBtrULHzzdZOvGobMLF34xXe3XdLVfwTzxPXGsIh67RGgzPECREX6DI08X5iXtmBu9DdtfHUx6ntzCqqoTaU0nElOdRNxD10eE0S9haRI2U84htjwFwyMx3GM9cI2J4RgVpEsihvVJN0yPuzAzIsYs2W3DYjZ3jfXC8rgTXuvvjZSNraLUawmcnkVxfBJhODkV5MdzDoVtH1KvrSiVE8x2eiqs8yiSXqslWkURCNep9FHWa/8B68EkTk+omttOof/qsWt+tWrsOmGtEmPNyd/FVWQ3ZykdLc1TjUa+6pNgB3OXCculCJKcGrHgJLiwjCEalCIdV2Ja9hse/TGEFW4asZC0uS74yHD8IcAaXehD0xgpb/DqpQS+0c+RNbZhVduGtPoLrOuE+RrJTWMH1rTtVOW2JjYMHYhMfAab5lfi2KAs9J8IC5gxP8T5ArVCehDV4B0i78SKuhcxRS9ylj5smggv+pAz99FBvSgt9tMNDFLbUEdo7rOr4FoREqHlT+T0N4AQNfPcbXCym4hM3kRK2Yb8C4pO044timjL2E62dhy5bgFBan7HDZinhogjI7SNzcrf4Rbe7XhhV/0Ch/JnOFV34dHcg5vg0Q7BJv8RxvEf4NXfh1N9Fy6CQ3WPzc2yn7CW0rI6JOP0OGg1ElyUgo0KUz8yKVRbmGdwVAwhl/ewea25nm7uYbK2DD44US6Xg8k0RQ/oNOxWBckpWuAhh80ix+yMEnrtM8ikf8Mxq6IHVd5Y58H7K0hOw2xWIJVKQkT0qNeBcrnKUKnUcXXs7u4hk9m4Zq/VWvuqVcEmqvFWtEhKpTK2snnkcm+xtZVHobALnc6I4WEJ8vkC2d+wtWw2h2LxpLGr3jigBlGdwuPBj3LpAna7BkuLKgSXtAiHdIhzJiwGVJifl4OLmRAK6pprZtMEDg7eC5QNHtEnhR87O/vwemSsSfmL5r/hzYwFh/s+vM07sbvjwcezpcbzv4HUihEcF29Gx1K+SujxyBuVSxGBG87ZEYSDSiy8kmIloUPhjbPxTa+SzhMm/p+wXK7Qb1JLKWmwHDYSkZ5S1MO/oKa0KU2yhUIG+lkZEKXn3mKexOFhK2V+/Ave+kePoR8JzQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 11 17 35 01&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-11-17-35-01-308c2c05327bb9cd29e81867ac87924c-6de41.png&quot; data-srcset=&quot;/static/2024-10-11-17-35-01-308c2c05327bb9cd29e81867ac87924c-e9163.png 200w,
/static/2024-10-11-17-35-01-308c2c05327bb9cd29e81867ac87924c-6de41.png 327w&quot; data-sizes=&quot;(max-width: 327px) 100vw, 327px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;存在以下情况不能应对：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;业务复杂度和产品迭代速度&lt;/li&gt;
&lt;li&gt;处理高并发、大数据量的用户请求&lt;/li&gt;
&lt;li&gt;代码维护和团队协作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是分布式系统引入了新问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网络传输的三态性&lt;/li&gt;
&lt;li&gt;数据的一致性&lt;/li&gt;
&lt;li&gt;可用性&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;技术体系&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;h3 id=&quot;单体系统的核心问题&quot;&gt;&lt;a href=&quot;#%E5%8D%95%E4%BD%93%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%A0%B8%E5%BF%83%E9%97%AE%E9%A2%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;单体系统的核心问题&lt;/h3&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;性能伸缩性：动态扩容对单体系统而言效率低下，如果通过简单扩容就能确保系统性能得到等比例提升，那么该系统就具备良好的伸缩性。对于单体系统来说，由于不同功能都在里面，即内存密集型和 CPU 密集型的代码都位于同一个服务器上，所以很难做到对资源的充分利用，如下图每个组件对资源利用率不同，无法针对组件级别进行资源利用率的提升&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-11-18-18-37-f7a87490d3be769cfd38e4fac0f7d51d-e2a73.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 728px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.26373626373626%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABvUlEQVQoz1WQW28SURRG59eY1MaW+KhV0wexP8OKiWnS+DvUpj5pqsYHExMuVgRrAy0QbEFa5sLMFGa4FAWKlUKVoGCjJuLyDDQxPqzsnX3W/rJzpJ1MmKK9hlUQWGvY1rjP7vqRd32osh9F9qFkfWjKeOa8FfL/XAfbeoOqvEJywqAiKJxSofclgRWc4yByjcKqGzvopijIea/SiMxRCrlpN8PCrQryAusUA8lJHwUN9THYHDXXOVy/Qjc+S957kWJghlrokuhn+BybpR29TKPiFW5Z7ORGQfwx+PVTRXJOHYqQ3jeN/vcc/ROdXl/DNF+iqV4MI0C1vDrC0H3kNDHTA3R7MgPhDsTOV+Gf/DAY/taRVPkFpeRtqvF5SpseKjFB3MP75AKHmUX24zfptjc4bkWoJT0cpBaovb0l/BuUN65TjM7TSIqaWOS4s4WU3n5GI3yBT6EpUg8msJ9PUfGdI/t4gmbQRSt4hg+Fp5SNJ/Sjk+z7p9lZOUs14BKei8yjSQax83wUX9SoR5E07TVF9SHm9h3M1D320v+jb92l00rQOUpipJbYe7eMKXBqPuNwHzO9hKWsUK/H+Qs93MOtqV1clwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 11 18 18 37&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-11-18-18-37-f7a87490d3be769cfd38e4fac0f7d51d-e2a73.png&quot; data-srcset=&quot;/static/2024-10-11-18-18-37-f7a87490d3be769cfd38e4fac0f7d51d-314ef.png 200w,
/static/2024-10-11-18-18-37-f7a87490d3be769cfd38e4fac0f7d51d-704a2.png 400w,
/static/2024-10-11-18-18-37-f7a87490d3be769cfd38e4fac0f7d51d-e2a73.png 728w&quot; data-sizes=&quot;(max-width: 728px) 100vw, 728px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码复杂度：修改一处代码容易引发连锁问题，不同组件间边界模糊，由于代码复杂度导致的系统缺陷会触发很多连锁问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;分布式系统的本质特性&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%9C%AC%E8%B4%A8%E7%89%B9%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式系统的本质特性&lt;/h3&gt;
&lt;p&gt;区别于单体系统，分布式系统会将整个系统拆分成多个能够独立运行的服务，这些服务在物理上是隔离的，相互之间基于网络进行通信和协调&lt;/p&gt;
&lt;p&gt;下图展示的就是现实场景中常见的一种分布式系统，可以看到这里有专门针对业务处理的业务服务 1 和业务服务 2 这两个独立的服务，也存在 Web 服务、消息中间件、缓存等提供技术能力的独立组件&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-11-18-24-39-aff9373768249719f1a557ca321d560f-8b86b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 725px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 45.37931034482759%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABi0lEQVQoz41RC0+CUBTm//+OHqbm2iq3amvL1pYpgihqggKiwMU0DS1R8etwLbPnOtu3C5zLOd9DwB9lGBVUlBTEwgGkUgrVShKmUea91SoirL5BYMyF53bBvB48rwunZ9PpIQimMM0GdP0aknROA8+gazkwZr4NXA/4WkJZTkNrZlAq7qJ5T6d4AMdx+OXFYonZbIHJ5BkPg0f+vFxGm4FRRCwJ0RYEp6fD7tRJTh6WqcJ1W9+2zudLzvg/Jdh2jSTWoSg5YqYifg+CgDcZ69ISkZZdoSxfomOJcB2DmKwwHo/RbknodCQY7SLdK8GyJAhKOUMMsyR1nxrHkKU0eWcitkdrijwIRU5SP4G6mkSjcYvp9AWa1kSxkEJNTeEuv0MLk2jpJxCGgwHiYHyf8XM4HGzoh2HIvzHmcei6jtHoadO37R6xMtHv+7zf930IP/nwW4KWVUXhLksKREr9ArXaDQ/uk4frn6MtfAx7TzKuOJRq5ZR8O4Jc2iOJCTTqhyRT5Z6G4YzCm+MV8O2g5XJOxs0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 11 18 24 39&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-11-18-24-39-aff9373768249719f1a557ca321d560f-8b86b.png&quot; data-srcset=&quot;/static/2024-10-11-18-24-39-aff9373768249719f1a557ca321d560f-b07e9.png 200w,
/static/2024-10-11-18-24-39-aff9373768249719f1a557ca321d560f-4ec81.png 400w,
/static/2024-10-11-18-24-39-aff9373768249719f1a557ca321d560f-8b86b.png 725w&quot; data-sizes=&quot;(max-width: 725px) 100vw, 725px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;想要实现分布式系统，首要的就是完成对系统中各个服务的合理拆分。通常，我们有两种主流的拆分方式，即纵向（Vertical）拆分和横向（Horizontal）拆分&lt;/p&gt;
&lt;p&gt;可以认为纵向拆分的目的就是更好地完成对系统中业务服务的合理组织。围绕一个完整而复杂的业务执行流程，我们通常可以根据不同的业务场景以及数据属性来完成对业务服务的拆分。例如，在常见的互联网医院系统中，具备最基本的医生、患者以及问诊等业务处理场景和数据，这时候我们就可以基于这些场景和数据来分别提取独立的业务服务，如下图所示&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-11-18-25-54-c04abd93b5815f873f8680f59fb2e668-3be6a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 726px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 30.165289256198346%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABLklEQVQY03VR207CQBTs//+CichDBTUi8UFQWhULVEuhtIBIBVqWkiA16QP0EoKMp/tACAknOdnNzM6cywo4iN1ux884TtDrVjBjEiyzCL15g3brFmbnDsytwDAesN3+7TWHKRwDaURRAntY5YZN7RpqI4d3NY8PNYep+4TPvsQNj7XcECfC93/BGCPziDqOEQQBkiTmWMqdCiHtZr0OSRjznM3mWCx+uEnaxWazhefN4ThTfk+xtMhy6ZO5t9eFYYTVKoRgmSVMxjIG/TKNUoLRLtDDIa82Hus0+iM6xj3tsQDXeca3rXGOMZt2WsTXgFbQKxMuUcoQ6jURuiaiplzgrZqB0brEZNLlon7vlfaXQV3JEndOHyPCsmTOjUYWTCMPhTQv8hka9Sw1c4V/9023bH6hwIkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 11 18 25 54&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-11-18-25-54-c04abd93b5815f873f8680f59fb2e668-3be6a.png&quot; data-srcset=&quot;/static/2024-10-11-18-25-54-c04abd93b5815f873f8680f59fb2e668-0aa07.png 200w,
/static/2024-10-11-18-25-54-c04abd93b5815f873f8680f59fb2e668-92365.png 400w,
/static/2024-10-11-18-25-54-c04abd93b5815f873f8680f59fb2e668-3be6a.png 726w&quot; data-sizes=&quot;(max-width: 726px) 100vw, 726px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;介绍完纵向拆分，我们再来看横向拆分。横向拆分的切入点在于复用，即我们在提取一系列独立服务的同时，还需要考虑通过一定的手段将它们高效地整合在一起。这样，整个系统就可以像是在搭积木一样对各个服务进行排列组合，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-11-18-27-12-1f59184611e036f2b4331e2dbfbb01b3-683dc.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 723px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 43.291839557399726%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB6ElEQVQoz11SYXOaQBTk//+Dfu+009jWNDZOjXbERkcskCCgBAFBUVEEHI02BsXt40zTaW+GuePesm/3LRxo2bYC27rCdFKFpn6CcncBf3SDkXdN9xJWqzX6/TJm0yq9lyGJH9DTLxHMaoQvYbNZ5zQ4nU7g8kMYTqgoI1x0CViHrtXheQKWS4VqYzw/Z9RMQRKr1LzJMEafxyrRqKmMND3+JQwCD5OJieGwD8O4xwM945EJRRHorMAfm5j457qqikQmwfcH0HURlqUxrO8biJZzRsrZVgOPGx6b9S115EkFz/bFvIZ+7yPU7gVk6R3En2+hqwVWi6M6wyVxHdvHFubBNxqFfCZ03SEpU9FqNTAa2QgCF7PZkOblsLPrGpCkJqm0z/cvz5xqnmeh3b7FYNAjfHAmnE07xP6DhluG54r4f223O8Rxgqen/T/3WXYiuzI5KMN8aJAjCTxfAKcqRbJTQL7rWoWAeBkwsFhEr0S73S8KKX6tp2lGI6mgq3xGu/UepnGN0tUbcIdDhsPhyIBhOMfAJAtmE/d3FQjCJdlzGEEeXEcokpMqU5SnHEVLlnCaHrDfp0w19yfufI3HFkm/gWOXKOEikug7HKfDmjl2m5KswR3Svzn4Qna/kuIp++54zAVlbP8NFwuMl69FAnYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 11 18 27 12&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-11-18-27-12-1f59184611e036f2b4331e2dbfbb01b3-683dc.png&quot; data-srcset=&quot;/static/2024-10-11-18-27-12-1f59184611e036f2b4331e2dbfbb01b3-4115b.png 200w,
/static/2024-10-11-18-27-12-1f59184611e036f2b4331e2dbfbb01b3-86845.png 400w,
/static/2024-10-11-18-27-12-1f59184611e036f2b4331e2dbfbb01b3-683dc.png 723w&quot; data-sizes=&quot;(max-width: 723px) 100vw, 723px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们注意到引入了一个分布式服务框架。通过该框架，系统中的医生服务、患者服务等一系列独立服务就可以进行合理地编排和整合，从而构建业务 A、业务 B 等不同的业务场景。显然，分布式服务框架在这里扮演了重要作用，而后续内容中所要介绍的 Dubbo 和 Spring Cloud 就是目前主流的分布式服务框架。&lt;/p&gt;
&lt;p&gt;请注意，分布式系统相较于单体系统而言具备优势的同时，也存在一些我们不得不考虑的特性，包括以下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;网络传输的三态性&lt;/p&gt;
&lt;p&gt;我们知道对于单体系统中的函数式方法调用而言，只有“成功”或“失败”这两种状态。但是分布式系统则不同，因为远程请求是通过网络进行传输的，而网络在处理请求时还会出现”超时”这个状态，这样就相当于有三个状态。&lt;/p&gt;
&lt;p&gt;显然，网络传输的三态性为系统开发带来新的挑战。面对超时状态，我们不能简单把它处理成是一种成功或失败，而是要具体场景具体分析，避免出现请求丢失或请求重复发送现象。在分布式系统设计过程中，我们需要考虑这种由于网络通信所导致的用户体验问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;请求的容错性&lt;/p&gt;
&lt;p&gt;从错误发生的几率而言，分布式系统显然比单体系统更加容易出错，因为系统的调用链路变得更长、更复杂。每个分布式服务自身可能会发生异常，而这种异常在整个调用链路上会进行扩散，最终可能导致整个系统都不可用。&lt;/p&gt;
&lt;p&gt;在分布式系统设计过程中，一大挑战就是需要确保部分服务的异常情况不会影响到整个系统的可用性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;系统的异构性&lt;/p&gt;
&lt;p&gt;分布式系统的异构性很好理解，原则上，每个服务都可以采用一套完全不同的技术体系来进行实现，只要它们对外暴露接口是统一的。但是，因为技术异构性的存在，会增加分布式系统的开发难度和维护成本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据的一致性&lt;/p&gt;
&lt;p&gt;在分布式系统中，各个服务通常都会构建属于自身的数据库，这样就会导致业务数据无法进行集中管理，也就无法通过传统的事务机制确保它们之间的一致性。如何实现数据的一致性是分布式系统构建过程的一大难点。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上几点是分布式系统的基本特性，我们无法避免，只能想办法进行利用和管理，这就给我们设计和实现分布式系统提出了挑战。&lt;/p&gt;
&lt;h2 id=&quot;解题要点&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;通过对单体系统的核心问题和分布式系统本质特性的分析，我们明确了一点，即从技术演进角度讲，分布式系统的诞生是单体系统发展的必然结果。因此，在回答这类问题时，对于技术演进的背景和需求的讨论是我们的第一点回答思路。这部分内容通常不用过多展开，我们可以结合日常开发场景，从业务需求和技术需求角度简要介绍即可。&lt;/p&gt;
&lt;p&gt;针对该类问题的第二点解答思路在于充分利用逻辑性和对比性。从面试题的考查方式而言，这是一道典型的对比类面试题。因此，针对如何介绍两个不同事物之间的差异，我们需要采用一定的逻辑性。一种比较容易掌握的技巧是：先抛出一个应用场景，然后分别对两种事物的正反面进行展开讨论。举例来说，如果我们想要阐述扩展性这一应用场景，那么对于单体系统而言，因为没有合理的业务边界和拆分策略，扩展性就很难保证；而对于分布式系统而言，通过引入合理的纵向/横向拆分机制就可以很好地解决这一问题。&lt;/p&gt;
&lt;p&gt;虽然这是一道概念类的面试题，但也不要忘记在回答过程中提及一定的实践内容。对于面试过程而言，不管是怎么样的面试题，面试官都希望从候选人身上看到相关的实践经验。因此，最后一个解题的要点，就是建议你可以从日常开发过程出发，基于分布式系统的拆分和集成、请求容错性的实现、数据一致性的不同应对策略等角度，谈谈自己对分布式系统构建的一些思考和总结，相信是很好的加分项。&lt;/p&gt;
&lt;h2 id=&quot;总结&quot;&gt;&lt;a href=&quot;#%E6%80%BB%E7%BB%93&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;总结&lt;/h2&gt;
&lt;p&gt;我们已经提到了分布式系统所具备的一组本质特性，也强调了我们需要对这些特性进行合理的应用和管理。为此，我们需要进一步梳理分布式系统开发过程中的技术组件。那么，实现分布式系统应该具备哪些核心技术组件呢？&lt;/p&gt;
&lt;h1 id=&quot;实现分布式系统应该具备哪些核心技术组件？&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%BA%94%E8%AF%A5%E5%85%B7%E5%A4%87%E5%93%AA%E4%BA%9B%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E7%BB%84%E4%BB%B6%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现分布式系统应该具备哪些核心技术组件？&lt;/h1&gt;
&lt;h2 id=&quot;背景-1&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h2&gt;
&lt;p&gt;分布式服务是构建分布式系统的基础，可以认为，任何一个分布式系统都是有若干个独立的服务所构成，这些服务通过网络通信实现相互调用，从而完成复杂的业务处理流程。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-06-52-d6fa862105e1347544eb3a97588033d0-a3fce.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 515px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 73.98058252427184%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACWElEQVQ4y4VTaW/aQBT0//8DVVWpn/qlSokaQKnSSAQoCTgOZyAYzE2CsbltDhtDpoM5Gi51pae38q5n5828J+DEen9/3+1fX1WUyzUUi2VMJtbJOx+XcA5sPB6jUkkyJIKJKJUeUa/HUa1mYNvzs6DCIdByuXRzs9GAUrxi9iLy5wvCwc9oq9fIv3ih673dP4egR4DbbNs2VLWKRkMmwzRemwqyz49otWpYXdmCHYIKp8rdkHSXqnawWGD3vd8fwHGcI4bb/RHDTqeNQiFG7eLI5e5xF7hESRHJVHHPHWcOTdNo0GTz39KVaRvCP9prWvm8BGf+AE29QSL+DcXCT16MQs6HYVmL3eOjkQHDnPzf5dlsgkxaRCoZpXYZsk27Gup6a8dotTStiVjsllWINEuhxnk0mzIEy7JhmmMCzTCzLLZHmbrpONef6wCSCT86+i+Isa8IBT+hrHhQq3ohJJPXbN47PGd8SKeuyO6Sez/F77ogCzqy1mex52YuF4csx1CQJbzkYtT4mX2aJWDCB70dgiR+x5P0g/RD1OuaGg1OsjQMg+XrrGg9NSZ1/NgVwnRqYTAYuWWPGSuxh0OTbg/IcsS9wXODjyh4e2txgsy9B0zTYBXOpq0Wx6asVr0uQ2tHUK8F6fItKuUA44aTk9iM5dSdFk3rQpLi7r7XH64Z7nf8mruipNDthFHI+ynDBXJZH0bDB/ZjnP1nM0fQ60Z57zfPPZTpniMapplvpydlPp+j3dbR6/VZdt/Nqqq54zib2WylIB0O02kPHsULggXQqAcoSRN/AdG4biqI+FjiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 06 52&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-06-52-d6fa862105e1347544eb3a97588033d0-a3fce.png&quot; data-srcset=&quot;/static/2024-10-12-10-06-52-d6fa862105e1347544eb3a97588033d0-96d1c.png 200w,
/static/2024-10-12-10-06-52-d6fa862105e1347544eb3a97588033d0-2b9d0.png 400w,
/static/2024-10-12-10-06-52-d6fa862105e1347544eb3a97588033d0-a3fce.png 515w&quot; data-sizes=&quot;(max-width: 515px) 100vw, 515px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;虽然，上图看起来并不复杂，但想要实现一套高性能、高可用、高扩展的分布式服务体系绝非易事。在日常开发过程中，我们在设计和实现分布式系统时需要系统梳理构建分布式服务的各个技术组件。而在实际的面试过程中这也是非常常见的一个面试问题，考查了候选人对分布式服务相关技术概念以及对应实现方案的理解程度。&lt;/p&gt;
&lt;p&gt;从面试角度讲，涉及分布式服务技术组件的知识体系非常广泛，所以可以认为这是一道发散型的面试题，面试官想要的并不是一个标准答案，而是看候选人对这一主题理解的深度和广度。针对面试不同岗位的候选者，面试官的要求是不一样的，判断的标准也或有所不同。&lt;/p&gt;
&lt;h2 id=&quot;问题分析&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;相信只要你开发过分布式系统，基本上都能对构建分布式服务所需要的技术组件说出个一二三来，比方说网络通信、远程调用。你可能也会提到负载均衡、集群容错这些更为复杂的名词。事实上，分布式服务的技术组件之间是有一定的逻辑关系的，有些组件能够独立运行，而有些组件则是构建在另一些组件的基础之上。比较典型的例子就是：配置中心的运行往往需要注册中心的支持，而服务容错则依赖于负载均衡的实现。&lt;/p&gt;
&lt;p&gt;另一方面，对于分布式服务而言，组件与组件之间的定位也有所区别。有些是必备组件，缺少了它们系统就无法运行。有些则是扩展性组件，比较典型的就是前面提到的配置中心，原则上我们不使用配置中心也照样可以构建分布式系统。而还有一些则是通用型组件，这些组件并不局限于只能用于构建分布式服务，例如动态代理组件。&lt;/p&gt;
&lt;p&gt;在面试过程中，关于这道题的问法其实有很多种，常见的包括：&lt;/p&gt;
&lt;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;在分布式服务构建过程中，经常用到的架构模式有哪些？&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;h2 id=&quot;技术体系-1&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在分布式服务的开发过程中，开发人员需要应用到一批技术组件。按照这些技术组件的不同定位，我们把它们分成三大类，即远程过程调用组件、微服务构建组件和通用技术组件，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-11-57-f6ca55d404165996531c2e028b54a091-51685.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 550px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.81818181818182%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACsklEQVQ4y21Sa1PaUBTM//8HrXZKtRWrUxTwhahVy5u8gLxDgEAgASIgBludyvYk0NoP3pkz9yab3bt7Tpjlcom/9bJ8Qbgk20basnAgy9gTBOzy/GrnuGhPmSYyqorn5+fo+/81GLyxxvMHCK6LgtlEXjPA2V2UrVZUN7KCas+BNhy9RQUT/Fwg+BVEtXh6xPRhir7rIJjN8DCfkYtHBMEMjmPj98sTne+xuL+HPx7CHbvEeeXPF3MwCSGJbXYX25U4YsXPeHf9AZvFGHTbxFXrFhuFGDZzW3j/I4aN3Cfs60moHR1nWhYx9gu2Kjv4WNzGVjWOhJ4C0x31oNgyaqaAUr0Ajc5G38CCnLsTF3JHQr0poljLQ6Fz020ieAwwmAygOypEg0e5UYTWU2ARxkx8Dx3rAjX+AKVcHC3zlJ6rUT9m0xFM7RQNMQm++g3ddhZ2m40wfzyg77IRjy3vwzJOCRfA+CRo6NdQlEvUxAxUNYtWS4hIk+kQkpSNSpYvoWkX6NiN1eBIUNeuUK+fo147h6aGmBQKuvC8IlrWd6hKBn3nFr1ebe3QQ9e+RtO8JHI2wtyBshKkVg29AqxmiJ3Dc3Po92UwY5qUJGUg8EeoVpKo1Y5gGqvId3cuBCENtpoEx6XRaByRezHCPK+LRv2YOAmUSwlKlyJT4ipyKMiyaRQLBxAFEjTZf4I8n6L3CRJNR5e1Wyv3o6FDgmckdkhFRsRjwupgQpLvV+H0crA7txSlhEF/1afZjP61QR69bo5iX9EgSuRMXQ3Fd+i5TJybqF0R5qprh40MOHJYKafopjDy2qE/oFZQXDb5GmsdeTRyaCBnhIWtIvfiCdrtBpghWTf0E4q0j3xuB7J0SJGLaxd9ipVApfSVsDgNLUkkLsJct0086mF5j1oSp3OaLuPwB0pWmrUmRwsAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 11 57&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-11-57-f6ca55d404165996531c2e028b54a091-51685.png&quot; data-srcset=&quot;/static/2024-10-12-10-11-57-f6ca55d404165996531c2e028b54a091-b4476.png 200w,
/static/2024-10-12-10-11-57-f6ca55d404165996531c2e028b54a091-1367e.png 400w,
/static/2024-10-12-10-11-57-f6ca55d404165996531c2e028b54a091-51685.png 550w&quot; data-sizes=&quot;(max-width: 550px) 100vw, 550px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们将对上图中的每一个技术组件做展开，从而为本课程后续内容的学习打好基础。&lt;/p&gt;
&lt;h3 id=&quot;远程过程调用组件&quot;&gt;&lt;a href=&quot;#%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8%E7%BB%84%E4%BB%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;远程过程调用组件&lt;/h3&gt;
&lt;p&gt;远程过程调用是分布式服务最基础的实现技术，开发人员需要从网络通信、远程调用、负载均衡、服务容错以及服务降级这五个维度来进行系统的理解。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;网络通信：网络通信是一切分布式操作的基础。当客户端和服务器端建立网络连接之后就可以相互发送消息。但围绕网络通信整个过程，事情并没有那么简单。我们需要考虑网络通信的性能、可靠性以及在通信过程中实现数据传输的方式，这就涉及到 IO 模型、可靠性设计以及序列化方式等一系列技术主题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;远程调用：远程调用解决的问题是如何发布远程服务以及如何引用远程服务。一旦服务发布成功，就相当于构建了一个有效的网络连接，并通过启动监听端口来对外暴露服务访问的入口；而服务引用则是一个向目标监听端点发起请求并获取响应结果的执行过程，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-13-23-67374f4de5a2d3ffcc840d3ba16f95dd-50124.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 545px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 27.88990825688073%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABK0lEQVQY021Ra2vCMBTt//8N+7DBGGxj85NTkFXtnLqJEwbDWk0fpsb04foaRZj1LE33YOiFww25OSc5JwqOVLEvsCt2ch0EAQzDgK7ryLJM7pWz8syxUlzXAmOOBKUmPtL0T/hzhyiKpGgYhthut//Inscg+auKv1rZUKJIA1+rYtjGe9hBd1RDz36GRnrQxg9I4+qCoiikcH8+xNAdoTlpYWbUBacLj6tC4x6+0FB8r4Wlcwe6bCDZtKG+XuHi7Ran43No095vBGVZloXLlxtcz2o46Z/Bpk0w2oAr4Nh1RJsOFJMMYJKhxGLxKLKawA8DcJ9Lu0mSyB7HMTjnYGuGIPRBXQp9OoBlVnzLfMJi3ocinGC/L19R2joMucyOECKR5/nhB/7wv/sXt/a9j3gep9YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 13 23&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-13-23-67374f4de5a2d3ffcc840d3ba16f95dd-50124.png&quot; data-srcset=&quot;/static/2024-10-12-10-13-23-67374f4de5a2d3ffcc840d3ba16f95dd-805e3.png 200w,
/static/2024-10-12-10-13-23-67374f4de5a2d3ffcc840d3ba16f95dd-95560.png 400w,
/static/2024-10-12-10-13-23-67374f4de5a2d3ffcc840d3ba16f95dd-50124.png 545w&quot; data-sizes=&quot;(max-width: 545px) 100vw, 545px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在服务调用过程中，远程调用本地化是基本要求，即远程调用过程的实现对于开发人员而言应该是透明的。同时，我们也需要考虑同步调用、异步调用以及同步转异步调用等一系列具体的调用实现策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;负载均衡：所谓负载均衡，简单讲就是将请求按照一定的策略分摊到多个服务实例上进行执行，基本结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-14-04-cb077cb9be8d474457da3cd9fa916135-d1477.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 536px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 67.3507462686567%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACtklEQVQ4y22TC1NaSRCF7///CdmtrbV2EYioZAXDwysJXuA+ALk8FQmCRBcVCwEJ8vzSc1m3kqp0Vdf0nO5zpme6RktWdTIri+zc3vrCJvNqkVk6JJ9S7LpBDr8c4SsFCLcjZAU/ezXJzK1trXByshpLk2RJRzuI+rAL4s4uhfMAluPDrYSI6e9IZ3c87Nx9T6EU9OJE6jdOPv1OSTCn4Cdf9GPZPqyij4NoAE3X/yFvByjmQ5RLYaxcAPf8gJS+w6fTHRxrDzPrx0j/LTX7JBN/cHryJ8bnvwQPUHBCwglimn4SegRttVoxGo0ZTya8TKf07/vkTJO7f/t8m73yOp9TrlS57nRYLFdMZzNuel+lwzKj8ZiZ1ChX3PV6jcYvbDgYMHt54WU0gs2G/u0tKxFWNnl+9vILEf6VaRshKGVlqsukI4O5vCRzcUFM4lS5TLpW43O1il4qEc/nyUneaDQ4cRzpbiusdJRrbxtl908DQtfXOBJnxQ05aK/d5qB7I3jHW03BM+KqJtRq8axu8YOG9qas7Gk4JOy6pB8eOLu/51gIh5UKH+o1wtKhiuNyYFpyZ4+PHJ4XmcjT/NThVmzzH7jm9uaGgQymK50VbItO+wu2DOlSrtjrdijKM3Sv217do9S9cd9EvSsvlxvPlfZwOOZxMGT6bfs2a8E6nZ5Me/U/UU2/0/3KYrH2ODJ8j+9d+aJR4Kpp0ry0ZLWpVtLk80m63SvqtRytK1tWw8ObUtdoWNzd9SgWdVw3xZXkm03Lq3NLZ2j12pHoVgUI02p+YDIyZF8kYxzx0D9ltbRo1ENc1PfZrB36gjlOisXcZDrJUK/ucduLCadCWb6pZhgJ4vEAx8d+otFdYvEgHz8GqNfLnOph4rEg0YifY/lWKk6ljmi3W8J5T0z2kciu5OSXJNR+n+/rnJ20JCZehAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 14 04&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-14-04-cb077cb9be8d474457da3cd9fa916135-d1477.png&quot; data-srcset=&quot;/static/2024-10-12-10-14-04-cb077cb9be8d474457da3cd9fa916135-bb263.png 200w,
/static/2024-10-12-10-14-04-cb077cb9be8d474457da3cd9fa916135-9a78f.png 400w,
/static/2024-10-12-10-14-04-cb077cb9be8d474457da3cd9fa916135-d1477.png 536w&quot; data-sizes=&quot;(max-width: 536px) 100vw, 536px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;负载均衡在实现上可以使用硬件、软件或者两者兼有。而针对软件负载均衡，也可以分成服务器端负载均衡和客户端负载均衡两大类。在分布式服务构建过程中，我们主要的讨论对象是基于软件的客户端负载均衡机制。例如，目前主流的微服务架构实现框架 Spring Cloud、Dubbo 等都内置了完整的客户端负载均衡模块。&lt;/p&gt;
&lt;p&gt;另一方面，负载均衡算法决定了对请求进行分发的效果。负载均衡算法有很多，可以分成静态和动态两个大类，它们之间的区别在于动态算法依赖于当前服务的运行时状态，这些状态信息通常包括服务过去一段时间的平均调用时延和所承接的连接数等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务容错：在分布式环境中，服务访问出错了该怎么办？这就涉及到服务可靠性问题。服务可靠性是分布式服务构建过程中的一项关键要素，我们需要引入容错思想和机制。常见的服务容错技术包括集群容错、服务熔断（Circuit Breaker）和服务回退（Fallback）等。下图展示的是添加了服务容错机制的系统架构演进过程：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-14-41-849ff2b076bd4a5c857d299bb7b72012-9e167.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 538px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 97.58364312267658%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAD0ElEQVQ4y31UaVfiSBTl/3+cDz0z7a6gssi+KgIhoCjZE5JAEkC60eG0p8UFXBDvvIRuR+3TU+fc815V7rv1qurl+fBhvLy8eHYymaDZTEORU4Q0ZCkFSUqjpWVgGKdY0F48/lv4Por9FByPr6CpcRIIw9D3yA9D4LdxcZ6Hrlfx8DB7l8DP4ftdhu4YfunhrN9Bx1Qhi6f4OnDg2Dpur8f/cX/wf5uhO+7ouFKrBd12YJ0N0JQVZA6K6JBvOF0opom2bb8TehV8O5nP5x6pOxggNRyifH+PgKZ52NF1bIgisqMRWOLGRQnPj4+/iPreHpGWPHtBQUlJxIFjI0E2xvNIiAKJ8MiZBva7DgpckwLmH29sceTZ/QzPD29A8+n4DvfXd5hNH3F5PkJbM/BM/v31BNOrWzzcTon7/Brjasyf5vCxah2Mw6LWPQLbrXuo9RZ+sVVC1aohL+8jWI2Qz6JkVFA2GdT7xxRTf0W9f+St+6JaAiyayH4vo3DDIH/NIHdVRuXxGP5WGEvcFpaaW1jhA1gRA/hUX0HusoTipIYcxeSvKsQnjCs4vGXhi2lp1JwMJCuDZjsF3lr4jJWmO8xCMJOQ7AxEmgv0neukkSW4XIGsSFzXat0cCu0kfOuMHw1pF6YcBtfYgiLswNL2UKUiDlVX4OhRSNyOty41A+g5aSxl/oDVTkA49cNQI5D5HYwGeUTZNfgcKlZVF+F0W7AdFxocsqohQm0r4Pk6Oh0F/bM2ZPkEmi5ANmQYpvQjRvNg2RoUXfr1T3k7ZtMn3N1M362NL8f/F7IQ7A17OJDKKMoMihJDfgUlhcVychP73CGubxYi7UEHG9lt7DFJlNUacSse341rUMNwG4oPVJt5voAUt4F6i+5OC4Gl160oQTTMGHabm9D6OuYPc4TrYVTNCFgtAkYNETfsgVGD2KV77A/PFoLZkxx0IpliCMrpDkwpDLHhx+UZbXS8CZnuav44R7AYwOhLAcrJNoT6lsfVhRDO7TTitNa7GNCvR9VdMViEKKM4CcakEKJiEAk5gpgQRJA26gwtzCZPyGp5xGgeF8OIEy9OG7u8PeLFqLwG5yQIamsHSgmsHoH1tQi1m4U52Pdsf1RGoumHTK/oZpgS0+DtJAzK3P2uEVzbI17kJAB70HMFX1CQiuDo7tqtGERuF7q6B+50G9/Oi8g2NiBYiieY5EisHaOaDEGirHjiCFSjl/+UED3apPbmLF75UCjj0+GfWK+uYa26ilVmZWEJfx1+RoearDuiRwn8XfmMNWb19btrl5llrFbWqUtd4F8/7IIyTdTYlQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 14 41&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-14-41-849ff2b076bd4a5c857d299bb7b72012-9e167.png&quot; data-srcset=&quot;/static/2024-10-12-10-14-41-849ff2b076bd4a5c857d299bb7b72012-bd273.png 200w,
/static/2024-10-12-10-14-41-849ff2b076bd4a5c857d299bb7b72012-7e34d.png 400w,
/static/2024-10-12-10-14-41-849ff2b076bd4a5c857d299bb7b72012-9e167.png 538w&quot; data-sizes=&quot;(max-width: 538px) 100vw, 538px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-15-16-2faa4d58d070168088565bc84667f3f7-50124.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 545px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 34.49541284403669%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABiklEQVQoz22RW0/bQBSE/f9/Ql8QNAhKSCS3QNuXohYS5UJiO8FOA7F3fb9EmDZtHohwvm4sWvWBI41mNaudPWeOtt1u+R/PVcWurudd3vsXHI1POJm2adpt3FBiGtcs7nXcxTn9XoPppIVlNAn8T0r7iMZrtYW+PaAje7Xx1V2Hvj+k/PlIEgvS1FJsMZt18NwhrjugyG/JMhttWZZkDw/kLyzCkNVqRVEUCCnJlzlpnhLHcf3XZrMhjiL1OGVZ5OR5ViNR9ztNe2eaHEynHBhGzS3HJvEj9OEZregDb3p77JuHNJxjvosFo+EXJtZbxqMGN4N9NfYet5NjFndtZk4LzVPu8yRhHiukKbYUlKrTIA0QhcRLBSITyFzytHlivV6pkT3yTHWvtN15Wfg1iiJ4PcPqueLGGdFT2Ri+xdg3GYmxyvAHaeITBiZBYCHlCwuDKJqoWFSGldrsP6gN/93ylejy+dclR84pp66OHl9wH3hY5jfWv78qkzM8Tyf0z3HsJo/lpdK7/AHeBffgRtcIhAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 15 16&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-15-16-2faa4d58d070168088565bc84667f3f7-50124.png&quot; data-srcset=&quot;/static/2024-10-12-10-15-16-2faa4d58d070168088565bc84667f3f7-805e3.png 200w,
/static/2024-10-12-10-15-16-2faa4d58d070168088565bc84667f3f7-95560.png 400w,
/static/2024-10-12-10-15-16-2faa4d58d070168088565bc84667f3f7-50124.png 545w&quot; data-sizes=&quot;(max-width: 545px) 100vw, 545px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，一级服务属于核心服务，需要确保高可用，不能执行降级操作；二级服务通常采用的是异步交互方式，容忍暂时的数据不一致性；而三级服务则可以按需对整个服务实行降级操作。&lt;/p&gt;
&lt;h3 id=&quot;微服务构建组件&quot;&gt;&lt;a href=&quot;#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%84%E5%BB%BA%E7%BB%84%E4%BB%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;微服务构建组件&lt;/h3&gt;
&lt;p&gt;在远程过程调用组件的基础上，我们继续讨论微服务构建组件，包括注册中心、服务网关、配置中心、消息通信和链路跟踪。这些组件扩展了分布式技术能力，为构建大规模分布式系统提供了技术保障。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;注册中心：在分布式服务构建过程中，服务与服务之间通过远程调用完成业务链路的构建。而在服务调用之前，我们首先需要发现服务，即解决在分布式集群环境下如何找到目标服务实例这一问题。服务发现和调用构成了服务交互的基础，整体流程下图所示，其中实线部分代表服务调用流程，而虚线部分则包含了服务的注册（Registration）和发现（Discovery）过程。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-16-02-1cfcf5ef6d37e4de84cdb14153b42586-fee7b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 547px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 64.71663619744058%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACPUlEQVQ4y42Ti1LaUBCG8/7PUEXbsaggFkTQVgFDQuQmFwUSiIpi62UsrVACQpKvhyDOtGXabuabc5KT/LO7/0bit3Bd93VdhOM43nn9sk4os85uNuixk90kmosgsSDmon+Lk1aRgr5FvRmhJjCvYnwsbyE5rsMviAzmmfR7PX70+wwGA4++2Pf7PSbPE44qMjsnfuI5Qd7PXsFPuBhcnOE07r8+st9uExMEKhUC5TLxqytizSbdxy6pUoq8EeSsEeLU2EY3w0J0HSlTzyA3Eih6ClWQqH1C7xh0v30XLxRQhYDWapGoVsmYJqXra1zH5cvjLYelBMlyyiNRSpLT80hheY20uoSsLKOqPuT8Cnv5OOPRmMloiKgdZzwG26bX7YpnI8bifjKeLKxMild3US9jKOcx1Is4qYsomnnsHU79nNozFCL2i7t39/c8PT3Nzr2+26/9tx0b6YPiJ1t8h6KtomXfImd9HBT3Z0ruTMR+fp4hhIVjnjnWwFqc4bF+7PUw3Uh6JGsHGDeGcNUiaxhUOh1yoodJYcy0f4bI0LEdzLYpnhcwHlrU7wyBTkN857nsJfKCqMCL24cHtmo1gkaTZU3DL1yOCJc3KmVuOp+pmhX2M284UnwcykukNR+bqSWk2RC73jVf53NoWRbWcMhIlDvt43TfE+WOrBEnzRLJZhjFiCDrYdH3KNFqaCa4iH9FqVUmUwtSLAfQxFCXqgF2tbU/B/t//+XT8zM2xJiF0isEjpbZVld4n1zlJ1c2uvgh8KfTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 16 02&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-16-02-1cfcf5ef6d37e4de84cdb14153b42586-fee7b.png&quot; data-srcset=&quot;/static/2024-10-12-10-16-02-1cfcf5ef6d37e4de84cdb14153b42586-9b56f.png 200w,
/static/2024-10-12-10-16-02-1cfcf5ef6d37e4de84cdb14153b42586-18701.png 400w,
/static/2024-10-12-10-16-02-1cfcf5ef6d37e4de84cdb14153b42586-fee7b.png 547w&quot; data-sizes=&quot;(max-width: 547px) 100vw, 547px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，图中的这三个服务都需要注册到注册中心以确保负载均衡器能够从注册中心获取各个服务的定义信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务网关：在分布式系统中，API 网关（Gateway）或服务网关（Service Gateway）的出现有其必然性。我们可以根据需要在服务提供者和消费者之间架设这层服务网关。在注册中心和负载均衡的基础上，添加了服务网关之后的系统架构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-16-32-c8b8eb700b3277f427c2a44e19d9a975-246ce.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 544px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 84.19117647058823%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAADgElEQVQ4y11UaXPaSBDl/3/bjZ1KpVKVxGAwh7kPYxtCwmGbSwcgifsQIAmEANtbji/eNmOzVFZUq1Xdb9709LzGNh4P0KjHIHAhZtWyH9VKALIUB89FYC7m2D26rqFei1E8AK4aQKXsY1YTaS0fhKapsHV7TYzVC4jCKREHUSq6KOmDOrpAp5OApk/+IxypQ4qnIDWCaNQCtLmbyH3oEq7XS1C+B9sWaM3nzORGA4Y2hbUwMZ1u/QL/f7YxjSo1CT83dLRkGTNNw9I0Wd7GyRKEbhclSUIolUKl2YSq6Sw5JmBVUcBTbGtcs4XWYMhy69s7cO02opkMcjwPsddDRZFh8wgC3P0+XLTApTQRoZ3OuSqmFLMnzhBfr3EsyfB0OkisVjjK5aDUaiiTeYjEQYU4GhLsW89xsBVEEVk6arZexxWR/iRfromsCplIc+0WslRlvtVEnkjL5J8fH9Ed9JHiObY2QxwZ2iBLnvUQLy/MPd7/A3OiYzIco620yVow1CnWpoXVfMG+W5LCcupgBHNq4PX301tzX1+BzQa2Db0Y59MLVXCNjJlHwSri16wAD+dHrJNEonOBZD+NsJKAlw8gZ17hallCyviFapdn6zfshz3h88MTUtIPJPRLXBoZRIdJ2ElCLs6LrwU7HOUTHFfccFY9ONfSb5hJElfKDVX3TrircEf6++EBC7qUpWUxu7+/Q596pZM81LGK4WjIYtY2v7QY9uX5rV07HttOXzPS1XRmYEYa1OnbXC7BiQIkktXukUlzPF3YYrUkzAwGTdHE0NiGm20Pt0fevqYU9BTdiIl+xGkCdj5IR/yePcJivcBqtcZJ4QT+kguJdwzDkblpYrqT/p6w1laQLjuh9+Jo8B50JJplwYu1noYz9Qmz2wUGIxWJayesCY0pYRXBh3rVjVvCRArfIPSkPWFn1ENI9OKMAJGbY8QrJ4gUnUjVTuG4PoJ5Z9Hg6wgLfiR5N0LXDoYJEzYl+uDlXGiO23vC/nTICPNUWUEJIS8HkSO7aUfgzH/DnCqcUc8idZKMHGD5LGFzZEXCnJYckNXWnrA97iNNhDpJpa2E0e9E0WmGsZpl4P/5Bcaa/ix0HRfUL3N6CbnuJ4GH0CTCu0UW50X7n0c2TAOfzj7jIPIBh5EDHEb3dhD9COt2SXK5x9fUd/wd/gsfo39iPsQO0X+/lH8ByW+rOcL/boIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 16 32&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-16-32-c8b8eb700b3277f427c2a44e19d9a975-246ce.png&quot; data-srcset=&quot;/static/2024-10-12-10-16-32-c8b8eb700b3277f427c2a44e19d9a975-745f6.png 200w,
/static/2024-10-12-10-16-32-c8b8eb700b3277f427c2a44e19d9a975-956e0.png 400w,
/static/2024-10-12-10-16-32-c8b8eb700b3277f427c2a44e19d9a975-246ce.png 544w&quot; data-sizes=&quot;(max-width: 544px) 100vw, 544px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;当然，并不是所有的服务调用链路上都需要添加这层网关，我们也可以根据具体场景直接通过负载均衡器进行服务访问。在实际应用过程中，这种混合式的服务调用管理方式也是一种常见的做法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置中心：面对不断增长的服务实例数量，传统的配置信息管理方式就显得无能为力。为此，在分布式服务构建过程中，一般都需要引入配置中心（Configuration Center）的设计思想和相关工具。下图展示了在前面各个组件的基础上添加配置中心之后的系统架构图，分布式系统中的各个服务都可能会依赖配置中心，从而完成配置信息的统一管理。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-17-34-090bc4a4289a3d094c7af7b01b73d97e-fee7b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 547px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 58.31809872029251%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAACMklEQVQoz31TDXeSYBTm//+InXmWZautWu6s1nTLbUqZolNApwgKyjwuREDlQ3i6YJqrne459/DCy314nw+YKIqw6TAKEZdpGmg2PkLkT8BVjyB1s2i3brGp3Zm/m8EzZdsWJOkGonCByo8sFLmA+/Z3zOcLBEGA/xXjOA69OIdtWfBcd7thzWzo+hii2IJhmHDsORaLBabTadKTySSZWdCs49i0bydr5ojnkSmXcdLt4qhYxFjXoQ2HOOaqOLyr4+Abi7cCj4IobOnG1aC5NMvidZXDcbOJLM3vnZ2B+dLp4FwUkZNl5Go1eIsl5s4cl0ILF+0OPvMCct0eaj35CaBpzZCl9/fzeXyg65Wi4F2hACYMfESky8rz1lQdC4MHFaOxBkm5R6lcxFAfQKNnqq7C9/2tLAbRHmkaHogRVitEq/CPKSHWX84Jl0h3DpHi0kjVXyJVS2OvcoBX4hu8EDKQR8oazDAS7ZNDkJbJ6WNTdi2PS51oqMo1cFId13e3qEocSjxL6xLqCg/XDwjIpSSswWLXTdPcysFsgNag4ZZO6IewyelkL4ggEGBHLELpVdASC9BUgeivEIbhU8BdoTdFUkDVRtg8nZozaFqcxfe4ud6HPjqH3LuiuCx//wjPnNDzfMhyJQkxVz1Fo35KQyweH3/CpXwOh2SKKhNwH4N+jzI6xHK5JOp2kst/AGNd+gqLnvQJbClDlPIE+DUJdwzoul7y0aTJ6fg+NiXujfMx1i+xoX+LXwdWwgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 17 34&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-17-34-090bc4a4289a3d094c7af7b01b73d97e-fee7b.png&quot; data-srcset=&quot;/static/2024-10-12-10-17-34-090bc4a4289a3d094c7af7b01b73d97e-9b56f.png 200w,
/static/2024-10-12-10-17-34-090bc4a4289a3d094c7af7b01b73d97e-18701.png 400w,
/static/2024-10-12-10-17-34-090bc4a4289a3d094c7af7b01b73d97e-fee7b.png 547w&quot; data-sizes=&quot;(max-width: 547px) 100vw, 547px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息通信：降低服务与服务之间的耦合度是分布式系统设计的一大目标，为此，我们可以引入事件驱动架构，基本组成如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-18-20-3e04a97d7e88a7189aea5813dd39e412-94f8a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 546px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40.476190476190474%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABm0lEQVQoz21RW3OaUBjk//+TPjRJjTVEm0nsDJ1pp7YgAqIBRU1ALl4AJ4rCdg8x7UP6zexwLrN7dlnJMhXYwwcMLYEuRqMunp9diCnLEmVVoTojWC5hmiYcZ4IgmJN3jyG5lnmP0bALY9CF1NeaFLrEQL/gwRW8aQu2/RPvhoLFfo+iOIglLOsX+toHmMYl9P5HjO0Gv58gpWmGOI6RJAlWqxV8P0AURTXGrgsvCLAIQ/zQNFjcBzzPswxhGGE6nZKzrrlxnGCz2UJ6b6TC4ukJEYWavR7afKTFvcz9HYXuGDlm9Igm/jcUrP4KidkzlkMn+XaLB8dBJ8/x2ffRWa+h8F7xPCz5wGw2e7OANw3xlU6nCofDsUZRnLDbvSDNcvjClaLgK8W/MdqNqkLWdXw3DCSMHRJpmuN4LMk91XyxlgZ6B+ORzMZuCRmu02ZzvX8Z2LSYw26HbLN5PaIhwxClXJHbZss3eBx3WKwsBL/wogFNvSaavGhjMbfPxPI10Pl3JMkKLh1PJh7m88daSP3dgEquOWix5Vv8Aa5QTaePiI4VAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 18 20&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-18-20-3e04a97d7e88a7189aea5813dd39e412-94f8a.png&quot; data-srcset=&quot;/static/2024-10-12-10-18-20-3e04a97d7e88a7189aea5813dd39e412-e2b97.png 200w,
/static/2024-10-12-10-18-20-3e04a97d7e88a7189aea5813dd39e412-db056.png 400w,
/static/2024-10-12-10-18-20-3e04a97d7e88a7189aea5813dd39e412-94f8a.png 546w&quot; data-sizes=&quot;(max-width: 546px) 100vw, 546px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;基于事件驱动架构，每一个服务既可以作为事件的发布者也可以作为事件的消费者，或者两者兼之。而事件也可以在不同的服务之间进行传播，从而满足各种特定的应用场景。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;链路跟踪：服务之间的调用不可避免会出现各种问题，这时候就需要引入分布式链路跟踪体系来定位和解决这些问题。基于每一次分布式请求，我们都可以捕获该请求的一系列跟踪数据，下图展示了基于 TraceId 和 SpanId 所构建的一次服务调用的完整链路。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-12-10-21-32-c8d330e7ddb517090ebee37e37994e43-fee7b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 547px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.411334552102378%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABLElEQVQY01WOXW/BcBSH+/2/wpJlkYgY9TKmHctSpVUsqGrRF4tdLZkLxjDMs79eLHHx5Jzk/M5zjpSzCihRjcfp0xUVv4oS1ig4ZeR+kdzggaJbQQlq8ew6r6IKR9bKIcl2EWPfxvhuYf10Ysxdm/pXk/ra4GWhkXEy3DtZqh/P6BsTbdX8z7YEl92LI2PnkaLoDV1PMfayWK1EjOfmMM07Go1bXFem203S7SRxhNiyEmjaDfYgHfeX/GQsC0eSIAiRTqczYdDCG5XRtRRGIy0CKmNXZTSsMLRL1LUkppEWRxUxqzLol+j3Cuj1FK+dPNOxgj/ROR5OSHDmePxls9kyn7+zXm8Iwxm93gDPm8T9crni83OBbQ8FDr4fEkUz8fEortvdnsPhyMX1B+c6VFXxPflUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 12 10 21 32&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-12-10-21-32-c8d330e7ddb517090ebee37e37994e43-fee7b.png&quot; data-srcset=&quot;/static/2024-10-12-10-21-32-c8d330e7ddb517090ebee37e37994e43-9b56f.png 200w,
/static/2024-10-12-10-21-32-c8d330e7ddb517090ebee37e37994e43-18701.png 400w,
/static/2024-10-12-10-21-32-c8d330e7ddb517090ebee37e37994e43-fee7b.png 547w&quot; data-sizes=&quot;(max-width: 547px) 100vw, 547px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;服务调用链路跟踪是分布式系统的基础需求之一，业界关于分布式链路跟踪也有统一的规范以及代表性的实现框架。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;通用技术组件&quot;&gt;&lt;a href=&quot;#%E9%80%9A%E7%94%A8%E6%8A%80%E6%9C%AF%E7%BB%84%E4%BB%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;通用技术组件&lt;/h3&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;资源管理：相信你对线程池、数据库连接池等技术并不陌生。这里的池（Pool）是一种对资源的抽象方法，代表一组可以随时使用的资源，但这些资源的创建和释放过程则基于一定的管理策略。资源池的应用非常广泛，存在多种具体的池化组件。&lt;/li&gt;
&lt;li&gt;框架集成：这里所说的框架集成，指的是 Dubbo、MyBatis、Spring Cloud 等主流的分布式开发框架与 Spring 框架之间的集成。我们可以基于命名空间以及自定义 starter 等机制完成与 Spring 之间的有效集成。理解框架集成的实现过程有利于掌握主流的分布式服务框架的运行原理。&lt;/li&gt;
&lt;li&gt;架构模式：架构模式描述某一特定应用领域中系统组织和表现的惯用方式。对软件体系架构模式的研究和实践促进了对软件设计的重用。在分布式系统开发过程中，也大量应用了诸如微内核架构、管道过滤器架构等架构模式，这些模式能够为开发人员提供具有高度扩展性的技术组件。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;解题要点-1&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;通过对技术体系的系统分析，我们明白了构建一个分布式服务系统所需要的各个技术组件。这里涉及了一大批专用名词。但是，光列举这些名称就够了吗？答案显然是否定的。&lt;/p&gt;
&lt;p&gt;我们在解释这些名词时需要做一些扩展，多提及技术组件的关联关系，从而确保回答过程具备较好的逻辑性。这是解答这个问题的第一点思路。例如，我们在介绍注册中心时，需要提到该组件与负载均衡之间的集成关系。&lt;/p&gt;
&lt;p&gt;针对该问题的第二点解答思路在于回归现实中的实践。本讲中介绍的技术组件都是很常见和很通用的，一般的分布式系统构建过程中都会使用到。你完全可以基于自己在日常开发过程中的应用情况来对这些组件做一定的展开。因为这是一道偏概念阐述的问题，所以如果能够做到理论联系实际，相信肯定能为你加分不少。&lt;/p&gt;
&lt;p&gt;最后，我想强调的第三点是技术判断力，你需要对各个技术组件背后的实现复杂度有一定的认识。对于那些复杂度较高的技术组件，可以更为细化地进行阐述，并在一定程度上显现出自己所具备的设计思想和对实现原理的理解能力，这样就能达到一定的深度。&lt;/p&gt;
&lt;h2 id=&quot;总结-1&quot;&gt;&lt;a href=&quot;#%E6%80%BB%E7%BB%93-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;总结&lt;/h2&gt;
&lt;p&gt;分布式服务的相关组件虽然比较多，但并不难梳理和串联。正如我们在前面所讨论到的，很多技术组件的出现是必然的，而有些组件之间具备一定的相互关联关系。在本讲内容中，我们从远程调用、微服务以及通用技术组件这三大维度出发来对这些组件进行了分类，并针对每个类别梳理了五大技术组件，以帮助你能够更好地理解和把握，从而形成自己的知识体系。&lt;/p&gt;
&lt;p&gt;在掌握了构建分布式服务的技术组件之后，下一讲我们将介绍基于这些组件所构建的分布式服务框架。实际上在本讲内容中，我们已经提到了 Dubbo、Spring Cloud 等框架，目前这些框架都非常主流。那么，这些框架都具备什么样的功能特性呢？&lt;/p&gt;
&lt;h1 id=&quot;网络通信：如何完成客户端和服务端之间的高效通信？&quot;&gt;&lt;a href=&quot;#%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AE%8C%E6%88%90%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%92%8C%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%B9%8B%E9%97%B4%E7%9A%84%E9%AB%98%E6%95%88%E9%80%9A%E4%BF%A1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;网络通信：如何完成客户端和服务端之间的高效通信？&lt;/h1&gt;
&lt;p&gt;从本讲开始，我们将进入到远程过程调用类技术组件的讨论，首先要讨论的是网络通信。&lt;/p&gt;
&lt;p&gt;分布式系统的构建依赖网络通信。相比单块系统的函数式调用，分布式环境下的请求和响应过程涉及到客户端和服务器端之间跨网络的交互和协作。这个过程一方面要考虑到网络的三态性，另一方面也需要考虑资源的利用效率。&lt;/p&gt;
&lt;p&gt;那么，如何设计并实现高效的网络通信机制？这是分布式服务框架的核心功能之一，也是我们在面试过程中经常会碰到的一个面试题。本讲内容将围绕这一问题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;背景-2&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h2&gt;
&lt;p&gt;在日常开发过程中，每一次分布式请求都会涉及到网络通信。网络通信表现为一个复杂且不可控的过程。然而，因为像 Dubbo、Spring Cloud 等主流的分布式服务框架都已经帮我们封装了网络通信过程，使得远程过程调用就像是在使用本地方法调用一样，导致了开发人员对网络通信过程的底层设计思想和实现原理往往不甚了解。&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;常见网络 IO 模型有哪些？各有什么功能特性？&lt;/li&gt;
&lt;li&gt;如果确保网络通信过程的可靠性？&lt;/li&gt;
&lt;li&gt;你认为网络通信包含的核心技术组件有哪些？&lt;/li&gt;
&lt;li&gt;如果让你来设计网络传输协议，你有什么样的一些思考？&lt;/li&gt;
&lt;li&gt;你能描述 Dubbo 框架中客户端和服务器端的网络通信实现过程吗？&lt;/li&gt;
&lt;li&gt;Dubbo 框架对网络通信过程采用了什么样的分层设计思想？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;分析&quot;&gt;&lt;a href=&quot;#%E5%88%86%E6%9E%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分析&lt;/h2&gt;
&lt;p&gt;网络通信的实现方式实际上有很多种，但因为网络通信过程复杂且不可控，因此如何使这个过程的代价降到用户可以接受的层次是分布式系统设计的重要目标。&lt;/p&gt;
&lt;p&gt;我们知道客户端和服务器端之间需要完成跨网络的交互过程，也就意味着两者之间需要建立网络连接。网络连接的创建和维护方式决定了通信过程的效率。同时，我们知道任何网络请求的处理过程都涉及到 IO 操作，而不同类似的 IO 操作方式对性能的影响巨大。在网络通信过程中，我们需要选择合适的 IO 模型。&lt;/p&gt;
&lt;p&gt;关于网络通信，可靠性是一个不得不提的话题。网络状态是不稳定的，网络之间的通信过程必须在发生问题时能够快速感知并修复。&lt;/p&gt;
&lt;p&gt;我们把上述分析点整理成一张图，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-4dc66.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 34.33476394849785%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABO0lEQVQY012NfU+CUBjF+f6fwrlms7QUnbNVZg6Quy5eXrp24cokQhBExXQOEzCmKzfPzp4/zn7nOczhT1mW5TeOfySppev1gVSB0i2EN2hQ1Y2mIt87jv2PncRclKNobRjtNAO6fqepZQleaVp5F/fDsEMpuSynaZplZ+eR43z2xS5C/dEIIyQOh4jnO4Ro+2R/7J7h8/JJ+aso+t7tcu6QJEkYLo5rh9VqHcfxBcz4vhcEzmzmLhZTw3gHgNtsNvN54PsOpZjjXsLQ9Tx7u90SggHgczJPgmDiuV8MLxQprcK3oqKUVOX6Y1iGUKD0YeI0gVgAoKCTimWxEHIYNzyvpcilHB7RmjVmGVluWNazqrKaxmLcNM02xrJti77fUxQWoZppPk6nPYwRpd3lUiCkpar18fjJdV9/ATkqc+nl7AiLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 49 00&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-fee1c.png&quot; data-srcset=&quot;/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-a67b7.png 200w,
/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-0b187.png 400w,
/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-fee1c.png 800w,
/static/2024-10-13-23-49-00-00f777f98faf517029466e8c0d70947d-4dc66.png 932w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图构成了我们回答这类面试题的基本思路。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-2&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在本节内容中，让我们来对上图中所展示的各项技术体系展开讨论。&lt;/p&gt;
&lt;h3 id=&quot;网络连接&quot;&gt;&lt;a href=&quot;#%E7%BD%91%E7%BB%9C%E8%BF%9E%E6%8E%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;网络连接&lt;/h3&gt;
&lt;p&gt;关于网络连接，我们要讨论的主要是长连接和短连接的概念。当客户端在向服务端发送请求并获取响应结果之后，这两种连接方式的区别在于对连接本身的处理方式。&lt;/p&gt;
&lt;p&gt;所谓短连接，是指一旦请求响应过程结束，连接自动关闭。而长连接则不同，客户端可以利用这个连接持续地发送请求并获取响应结果。显然，采用何种连接方式没有统一的标准，而是要看具体的应用场景。有时候，考虑到性能和服务治理等因素，我们也会把短连接和长连接组合起来使用。&lt;/p&gt;
&lt;h3 id=&quot;io-模型&quot;&gt;&lt;a href=&quot;#io-%E6%A8%A1%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;IO 模型&lt;/h3&gt;
&lt;p&gt;任何网络请求处理过程都涉及到 IO 操作。&lt;/p&gt;
&lt;p&gt;最基本的 IO 操作模型就是阻塞式 IO（Blocking IO，BIO），该模型要求服务器端针对每次客户端请求都生成一个处理线程，因此对服务器端的资源消耗要求很高。&lt;/p&gt;
&lt;p&gt;非阻塞 IO（Non-Blocking IO，NIO）和 IO 复用（IO Multiplexing）技术实际上也会在 IO 上形成阻塞，真正在 IO 上没有形成阻塞的是异步 IO（Asynchronous，AIO）。&lt;/p&gt;
&lt;p&gt;各个 IO 模型效果如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-50-30-87e48e4ba0882d82e7c47e4f3080e969-ba470.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 649px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.38212634822804%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABUUlEQVQoz12RyW4CMQyGef/n66mXUgFDmcls2cZrUicICZFDZNn+vPw+XZy/rwEJDwCzf8YtHkCIWzp+p2YDwm3ez+M6bsH85hmW3WcQldMSsvNJmAkpAc0hiwgiJsBh8URsNjID2WMAYBFrZvXVYIScw6Kl1qJzhK+Lq7USswrHzZVu19dDIvszsRaL1FPho+RFirGyZvz+W3uSATI+hoBWRVpiz34vVBpMB3jXYd0z2G4NZqlK0V1JK78B73B9wtfhwlpFi0JSCOY1YNrj+Xo7bEAVW6eLR/wJ83H0zpZWMTk3oRRTxSS0nbl3tkhaHg39HJtB8mZhUa2U13U2mGzsKo+/m2/qNmAZB+ezTdF3f8EKkf1Dax877xKeaksVzO4qbYWusLswy+fYq4/Tulu2ieRjGqbZqkC7OdzdbEexCwPxbTSbY867D+e7WzbLTf+8lEV53+SpVQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 50 30&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-50-30-87e48e4ba0882d82e7c47e4f3080e969-ba470.png&quot; data-srcset=&quot;/static/2024-10-13-23-50-30-87e48e4ba0882d82e7c47e4f3080e969-47540.png 200w,
/static/2024-10-13-23-50-30-87e48e4ba0882d82e7c47e4f3080e969-ee316.png 400w,
/static/2024-10-13-23-50-30-87e48e4ba0882d82e7c47e4f3080e969-ba470.png 649w&quot; data-sizes=&quot;(max-width: 649px) 100vw, 649px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&quot;可靠性&quot;&gt;&lt;a href=&quot;#%E5%8F%AF%E9%9D%A0%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;可靠性&lt;/h3&gt;
&lt;p&gt;因为网络状态是不稳定的，所以业界诞生了一系列手段来确保网络通信的可靠性。&lt;/p&gt;
&lt;p&gt;首先，我们需要对网络的通信链路进行有效性的检测，这方面最常见的实现机制就是心跳检测。我们可以在网络协议的 TCP 层发送心跳包，也可以在应用层协议上传递包含一定业务数据的心跳信息。&lt;/p&gt;
&lt;p&gt;另一方面，一旦检测到网络通信出现问题，那么就需要采取一定措施来恢复网络状态。这方面主流的做法就是断线重连。针对不同场景，我们可以采用不同的重连次数和频率，直到网络重连成功或者超时。&lt;/p&gt;
&lt;p&gt;讲到这里，我们需要注意，上述关于网络通信相关技术体系的讨论都是相对抽象的内容。在具体面试过程中，如果只是简单给出这些概念性的描述，显然无法满足面试官的要求。要想充分展示候选人对网络通信的掌握程度，就需要依托一定的开源框架。只有通过这些开源框架，我们才能了解网络通信的底层原理。&lt;/p&gt;
&lt;p&gt;在接下来的内容中，我们将基于目前主流的分布式服务框架 Dubbo 来分析该框架针对网络通信采用了什么样的设计思想和实现过程。&lt;/p&gt;
&lt;h2 id=&quot;源码解析&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在 Dubbo 框架中，存在一个独立的 Remoting 模块，封装了对整个网络通信的实现过程，该模块的组成结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-4a27d.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 16.259298618490966%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAIAAAAcOLh5AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAsElEQVQI1w3C3QqCMBgAUN//KRJDrOjHsmYRdJmjqZ9Lp5uBOiXMrryuaR2OptR3GJRSn3EciwIimEewyLJT0zzz/Px6XYTYU7ri3MnS3Z2u/hmz2/aS5ycNQhtgQ+laiOh6dcPA8InBEqcsqzi2k9jC3iQC0ydTQgyMdezpEJqCLxnbau9303V118m+7zn3AKwgsB6Pc1XVaYqkRGWJZIXIzSTEBJgVxUFKt26OnKMfAq6SautRgPEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 51 30&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-fee1c.png&quot; data-srcset=&quot;/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-a67b7.png 200w,
/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-0b187.png 400w,
/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-fee1c.png 800w,
/static/2024-10-13-23-51-30-b17503862f9908d341c25b33b73bc9a4-4a27d.png 941w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从组成结构上讲，Remoting 模块主要包含三个组件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exchange 组件。信息交换层，用来封装请求-响应过程。&lt;/li&gt;
&lt;li&gt;Transport 组件。网络传输层，基于 Netty 等框架抽象统一的网络通信接口。&lt;/li&gt;
&lt;li&gt;Serialize 组件。序列化层，主要完成数据的序列化和反序列化过程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而从技术分层上讲，Remoting 模块处于整个 Dubbo 框架的底层，是我们后续要介绍的服务发布和服务消费的基础。显然，Remoting 模块的组件呈现的是一种对称结构，即 Dubbo 的生产者和消费者都依赖于底层的网络通信。所以，我们也将分别从服务器端和客户端两个角度出发分析 Dubbo 中具体的网络通信过程。&lt;/p&gt;
&lt;p&gt;然后，在 Dubbo 中，真正实现网络通信的过程委托给了第三方组件。Dubbo 通过 SPI 的方式提供了与 Netty、Mina 等多种通信框架的集成方式。这部分内容相当于是对上图中 Remoting 模块的补充，从层次上讲应该属于 Dubbo 框架的最底层。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-服务器端通信原理&quot;&gt;&lt;a href=&quot;#dubbo-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 服务器端通信原理&lt;/h3&gt;
&lt;p&gt;我们首先介绍 Dubbo 服务器端的网络通信过程。请记住，Dubbo 中服务器端通信的目的就是集成并绑定 Netty 服务从而启动服务监听。我们关注 Exchange 信息交换层和 Transport 网络传输层这两个核心层之间的交互和协作过程，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-758d9.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.70960929250264%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA50lEQVQY03WNT2+CQBDF+f6fwIsee2rSNEratCQWmjSNFAouJewfQFmmddFl4aCcQJcevPkymbzfm7yMQelbHD8x9goQK9USYmv0/XkQLPSkmZVlHoBIkm+ETJ2HoRmEC72j6NkQIgFYVyJqm9/jsasELsuAUpfSFaFuJX6kLIbhvN/DZuOl6RdjLsafee5xHhrnG9KFW/n1ZAyj+uE/6Pteqb/Dge92+XabaCMlV01V16pp5Ih1WRQYgCkFGq+fx3LXdYy9+P7dcjl17Kltzwh9IOS9bU8IfZTcjNC9ZU0cZ5ayOcaPFzrgCtGDPkBuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 52 48&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-fee1c.png&quot; data-srcset=&quot;/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-a67b7.png 200w,
/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-0b187.png 400w,
/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-fee1c.png 800w,
/static/2024-10-13-23-52-48-78ae3ae6ed9ebc61e627bd5effa70be9-758d9.png 947w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中涉及了 ExchangeServer、HeaderExchange、NettyTransport 和 NettyServer 等一系列核心对象。显然，通过这些对象从命名上就可以很明确地将它们划分到 Exchange 和 Transport 这两个层。&lt;/p&gt;
&lt;p&gt;在 Dubbo 中存在一个 Protocol 接口，该接口是 Dubbo 中最基本的远程过程调用实现接口，完成了服务的发布和调用功能。而在 Protocol 接口中存在 export 和 refer 这两个核心方法，其中前者用于对外暴露服务，后者则用来对远程服务进行引用。针对 Protocol 接口，Dubbo 提供了一组实现类，其中最重要的就是 DubboProtocol。&lt;/p&gt;
&lt;p&gt;我们从 DubboProtocol 中的 export 方法进行切入，该方法会根据传入的 URL 对象创建一个 Exchange 服务器，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53370929347815060000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void openServer(URL url) {
        String key = url.getAddress();
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                serverMap.put(key, createServer(url));
            } else {
                server.reset(url);
            }
        }
}`, `53370929347815060000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isServer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IS_SERVER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isServer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;ExchangeServer&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                serverMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们来看 Exchange 服务器的创建过程，对应的 createServer 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;239141914235241340&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private ExchangeServer createServer(URL url) {
        // 省略其他代码
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        }
        return server;
}`, `239141914235241340`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeServer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 省略其他代码&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ExchangeServer&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exchangers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这里的关键代码就是通过 Exchangers 的 bind 方法创建了 ExchangeServer，这个过程依赖于一个 Exchanger 接口。那么这个 Exchanger 接口是做什么用的呢？该接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;4482167209420651000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(HeaderExchanger.NAME)
public interface Exchanger {
    @Adaptive({Constants.EXCHANGER_KEY})
    ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;

    @Adaptive({Constants.EXCHANGER_KEY})
    ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
}`, `4482167209420651000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HeaderExchanger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exchanger&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXCHANGER_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ExchangeServer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXCHANGER_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ExchangeClient&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Exchanger 接口只有两个方法，一个是面向服务器端的 bind 方法，一个是面向客户端的 connect 方法。在 Dubbo 中，Exchanger 的实现类只有一个，即 HeaderExchanger，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19346116677448520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class HeaderExchanger implements Exchanger {
    public static final String NAME = &amp;quot;header&amp;quot;;

    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
}`, `19346116677448520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeaderExchanger&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exchanger&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeClient&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeaderExchangeClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transporters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DecodeHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeaderExchangeHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeServer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeaderExchangeServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transporters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DecodeHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeaderExchangeHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，上述 HeaderExchanger 在创建 HeaderExchangeServer 的同时也加入心跳检测功能，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;42740718569981920000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void startHeatbeatTimer() {
        stopHeartbeatTimer();
        if (heartbeat &gt; 0) {
            heatbeatTimer = scheduled.scheduleWithFixedDelay(
                    new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
                        public Collection&lt;Channel&gt; getChannels() {
                            return Collections.unmodifiableCollection(
                                    HeaderExchangeServer.this.getChannels());
                        }
                    }, heartbeat, heartbeatTimeout),
                    heartbeat, heartbeat, TimeUnit.MILLISECONDS);
        }
}`, `42740718569981920000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startHeatbeatTimer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;stopHeartbeatTimer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;heartbeat &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            heatbeatTimer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scheduled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scheduleWithFixedDelay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeartBeatTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeartBeatTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ChannelProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unmodifiableCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                                    &lt;span class=&quot;token class-name&quot;&gt;HeaderExchangeServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; heartbeat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; heartbeatTimeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    heartbeat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; heartbeat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 HeartBeatTask 的 run 方法中，我们根据所配置的 heartbeat、heartbeatTimeout 等相关心跳属性，执行 channel.reconnect、channel.close 等方法。这也是心跳检测机制的一种常见实现方式。&lt;/p&gt;
&lt;p&gt;注意到，我们在 HeaderExchangeServer 中终于看到了属于 Transport 层的对象 Transporters，接下来我们来看服务器端 Transport 相关的组件。&lt;/p&gt;
&lt;p&gt;站在服务器的角度，网络通信过程的目的只有一个，就是装配服务并启动监听，从而接收来自服务消费者的访问。而对于服务消费者而言，通信过程的目的无非是对远程服务进行连接、发送请求并获取响应。因此，在 Dubbo 中存在一个 Transporter 接口，该接口提供了 bind 和 connect 方法分别对这两个基本操作进行封装，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54471371316914060000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;netty&amp;quot;)
public interface Transporter {
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}`, `54471371316914060000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;netty&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SERVER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TRANSPORTER_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLIENT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TRANSPORTER_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们发现 Transporter 接口的定义与 Exchanger 接口非常类似，两者同时提供了 bind 和 connect 这两个方法。区别在于 Exchanger 中用到的 ExchangeHandler，而 Transporter 中用到的是 ChannelHandler，显然 ChannelHandler 面向消息通信的通道，提供了比 ExchangeHandler 更底层的操作语义。&lt;/p&gt;
&lt;p&gt;与 HeaderExchanger 不同，Dubbo 中针对 Transporter 接口提供了一批实现类，包括 GrizzlyTransporter、MinaTransporter 以及两个 NettyTransporter。系统默认会加载 org.apache.dubbo.remoting.transport.netty 包下的 NettyTransporter，该类如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69066412058316005000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class NettyTransporter implements Transporter {
    public static final String NAME = &amp;quot;netty4&amp;quot;;

    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }
}`, `69066412058316005000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyTransporter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;netty4&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里看到了真正实现网络通信的 NettyServer 类，NettyServer 实现了 Server 接口并扩展了 AbstractServer 类。在 AbstractServer 中，Dubbo 提供了对网络服务端的通用抽象，即抽象出 open、close、send 等一组面向网络通信的通用方法。而 NettyServer 作为 AbstractServer 的子类，它的启动监听实现代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;39140483861723595000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected void doOpen() throws Throwable {
        ...
        bootstrap = new ServerBootstrap(channelFactory);

        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
        channels = nettyHandler.getChannels();
        bootstrap.setOption(&amp;quot;child.tcpNoDelay&amp;quot;, true);
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast(&amp;quot;decoder&amp;quot;, adapter.getDecoder());
                pipeline.addLast(&amp;quot;encoder&amp;quot;, adapter.getEncoder());
                pipeline.addLast(&amp;quot;handler&amp;quot;, nettyHandler);
                return pipeline;
            }
        });
        channel = bootstrap.bind(getBindAddress());
}`, `39140483861723595000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
        bootstrap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServerBootstrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelFactory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyHandler&lt;/span&gt; nettyHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nettyHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        bootstrap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOption&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;child.tcpNoDelay&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        bootstrap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPipelineFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelPipelineFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelPipeline&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;NettyCodecAdapter&lt;/span&gt; adapter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyCodecAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCodec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;ChannelPipeline&lt;/span&gt; pipeline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;decoder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDecoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;encoder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEncoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nettyHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pipeline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bootstrap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBindAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;熟悉 Netty 框架的开发人员对上述代码一定不会陌生，这里使用 Netty 的 ServerBootstrap 完成服务启动监听，同时构建了 NettyHandler 作为其网络事件的处理器。然后 NettyServer 的 doClose 方法基于 Netty 的 boostrap 和 channel 对象完成了网络资源释放。&lt;/p&gt;
&lt;p&gt;这样我们对 Dubbo 中与网络通信相关的服务监听启动和关闭，以及发送消息的过程就有了整体的了解。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-客户端通信原理&quot;&gt;&lt;a href=&quot;#dubbo-%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 客户端通信原理&lt;/h3&gt;
&lt;p&gt;有了对前面服务器端各个技术组件的介绍，理解 Dubbo 客户端通信原理就会容易很多。我们在介绍服务器端时所引入的 Transporter 接口同时包含了对客户端方法的定义，而 Transporter 的实现类 NettyTransporter 也同时提供了 NettyClient 类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;87780464512349810000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class NettyTransporter implements Transporter {
    public static final String NAME = &amp;quot;netty&amp;quot;;

    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }
}`, `87780464512349810000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyTransporter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Transporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;netty&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;与 NettyTransporter 相关的这些核心类之间的关系下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-8f73d.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.409989594172735%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABkklEQVQoz22R227iMBCGef936NUWthRWotWu1PaiVbVSRY40qUMOjoGG4BpiYoemISEQ6oBEtauORpb+8Xz6Z+yGadxb4MG2H1/MO0pDQqCmdTW1Bz0hSZKssizz0V9dv3oxr4NApTRCvuS5TxBKDUW9cL2eY/dMsxUElu8rqnqmKj8cpxOGoyxbc84RurHAT8+7HI8fXHdoGO3BoNmXmg3GlpyLjOM4KooijgmeAUohxsM0Xe33++12i2fe6+uzyPl8nOe56OF8IeZq7P8NcccYSxKeJInAjvB7mry94eVyuVrxqqpOzUe41lW1EychQ9vuOvZVGN6FszGlMcZhENyKvRyn5/t/yrI4NNfx5XyEp9PngX4uy03L+jWfh6ISRcQwO5rWUpRzqd/abPL/nI9w7Z+m74vFDOMJY1Ge1yZluRWPQsg0ijBjdLfbfQMf1ishVBCSXbc/GsmEIEFCKI+QIioIKb4vTSbGae0TXOvNptC0CwAuh6Arfg6hp/W60PUOAG1QV9oWaJvm74+PrDzEJ1aS5QpZ25rGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 56 50&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-fee1c.png&quot; data-srcset=&quot;/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-a67b7.png 200w,
/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-0b187.png 400w,
/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-fee1c.png 800w,
/static/2024-10-13-23-56-50-01ae14865b684dc759f85006d2b7c9f8-8f73d.png 961w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中的很多类我们都已经明确了，因此在介绍 Dubbo 的客户端通信原理时，不会像服务端那样做全面展开，而是更多关注于客户端本身的特性，所以我们的思路先从底层的 NettyClient 类进行切入。&lt;/p&gt;
&lt;p&gt;与 NettyServer 类类似，NettyClient 也存在一个抽象的父类 AbstractClient。作为网络客户端的通用抽象，AbstractClient 这个模板类一共提供了 doOpen、doConnect、doDisconnect、doClose、getChannel 这 5 个模板方法。与服务器端所提供的 3 个方法相比，客户端还需要实现与 Connect 这一操作相关的两个方法。&lt;/p&gt;
&lt;p&gt;NettyClient 中的 doOpen 方法如下所示，这里创建了 ClientBootstrap 并完成初始化参数设置。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27650436406577672000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
protected void doOpen() throws Throwable {
        ...
        bootstrap = new ClientBootstrap(channelFactory);
        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast(&amp;quot;decoder&amp;quot;, adapter.getDecoder());
                pipeline.addLast(&amp;quot;encoder&amp;quot;, adapter.getEncoder());
                pipeline.addLast(&amp;quot;handler&amp;quot;, nettyHandler);
                return pipeline;
            }
        });
}`, `27650436406577672000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
        bootstrap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientBootstrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelFactory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyHandler&lt;/span&gt; nettyHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        bootstrap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPipelineFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelPipelineFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelPipeline&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;NettyCodecAdapter&lt;/span&gt; adapter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyCodecAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCodec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;ChannelPipeline&lt;/span&gt; pipeline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;decoder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDecoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;encoder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEncoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nettyHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pipeline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;和 NettyServer 一样，NettyClient 同样完成了对 Netty 框架的封装。在 NettyClient 的 doConnect 方法中，同样使用 ClientBootstrap 完成与服务端的连接和事件监听。而 doDisconnect 方法则用于移除当前已经断开连接的 Channel。然后，和 HeaderExchangeServer 类类似，在 HeaderExchangeClient 类中也添加了定时心跳收发及心跳超时监测机制。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-2&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;对于远程过程调用而言，网络通信还是一个偏向于概念的技术考点。因此，在回答这类问题时，第一步需要针对网络通信这个话题本身给出一些说明和描述，这属于理论知识体系，是需要死记硬背的基础内容。基于对这一话题的了解，通常我们就可以引出一些自己比较熟悉的点，围绕这些点来进行展开和引导即可。例如，对于网络传输协议，如果你了解 ISO/OSI 模型，那就可以基于这个模型来对类似“消息头”这种自己比较熟悉的点进行发散。&lt;/p&gt;
&lt;p&gt;第二个解题要点是对网络通信实现过程进行抽象化、系统化的阐述。在目前主流的分布式服务框架中，对于网络通信模块都会有自己的一些抽象和提炼，其内部结构往往比较复杂。以本讲中介绍的 Dubbo 框架为例，就采用了明确的分层架构。每一层中都包含了一些核心的接口，这些接口之间的继承关系以及所处于的层次如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-13-23-58-54-caa89dfe805e3b7c2581d16ab246c1b8-1dace.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 770px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACLUlEQVQoz4VRXVPaQBTN/3/tS6cPtiLakdEWRFskHadaNJZ8Jwhk2WSzWQTkoyQQCPmAdAF1tA/1zM7O3Xvu2b33LCNJedP8zvM5UTxqNM4ajQLGzTRNXXekaXnLLFerOVE4uqudOM55s8mla6w2e8rUal9hK8//3q9UPmpqDmNadEcJQpCuH1rmyS2Xua7sKMqhjQqgdfNKzLLvVTUry3t89ZOuHXDcBwBkSvR6hC2/oxlJzEjirixnRWFHEEqUijeIooipN24dR7YsHkIaKIbBdbuEVkynXr3BYUeBsEoXxjJCvI2b83nQ6XQIIe12m4njZZKkvh/2+y4N4jgNgmA6nfr+LIpWNON5wZaix8UipNR4PHZd1/M8ZjDomqZiNHlJunScGkL11epxpOGwZ9uaAURJ+uk4OkJrL2i3nQ16vR6DsUqcvKp+FoUsIUUASvP5YrlM6A0ACN3uGc0L/J5lHUNYCsN4bdfT7Yxtqw8PJVXL8Xx2OPhhGCXa8MaPhPYy6LOCsM/dZEyYN+H5YhGnL8C0WkqtdmzbZfrblvVNFItxnGw5CDVgFDFmbVRGqCRLp8/UoziJk/lsMRq5bdKhQRCEzxwdz1v7Mlkv1/N9P32NtWE2UiGUFOXq/r6O7PryaaQ3wRCiEVJQ5ANZ2se4YFns1pUtVq/xrxhjnZDTm+vdX1c7AHwBoPxS/MbL0JSxU/wzvuz3L0ajC10/jaL/icMwnEwmdP7ZbPYXY/zztQAnnwoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 13 23 58 54&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-13-23-58-54-caa89dfe805e3b7c2581d16ab246c1b8-1dace.png&quot; data-srcset=&quot;/static/2024-10-13-23-58-54-caa89dfe805e3b7c2581d16ab246c1b8-f7e89.png 200w,
/static/2024-10-13-23-58-54-caa89dfe805e3b7c2581d16ab246c1b8-20ec6.png 400w,
/static/2024-10-13-23-58-54-caa89dfe805e3b7c2581d16ab246c1b8-1dace.png 770w&quot; data-sizes=&quot;(max-width: 770px) 100vw, 770px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;针对网络通信，我们要明确 Dubbo 框架集成了很多第三方工具，这部分工具只关注于底层的具体网络通信过程。Dubbo 把这一过程抽象成一个 Transport 层，即网络传输层。而 Dubbo 又提供了一个 Exchange 层，用于封装请求和响应，称为信息交换层。从功能职责上讲，Exchange 偏向于 Dubbo 对自身通信过程的抽象和封装，跟具体的网络通信关系不大，具体的网络传输工作都是由 Transport 层负责完成。理解 Dubbo 框架的这种设计思想和实现方式，一方面有助于提升面试内容的丰富程度，另一方面对于更好地理解框架实现原理也很有帮助。&lt;/p&gt;
&lt;p&gt;在面试过程中，我们需要确保整个针对网络通信的分析过程是理论结合实践的，如果你能够把本讲内容结合起来进行讲解，相信这道面试题会成为你区别于其他候选人的一个亮点。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲介绍了分布式系统远程过程调用部分的第一个技术组件，即网络通信。可以说网络通信是一切分布式系统的基础，我们从网络连接、IO 模型等角度讨论了与网络通信相关的一些基本概念。&lt;/p&gt;
&lt;p&gt;本讲的核心内容是对 Dubbo 中网络通信具体实现过程的深入分析，我们从源码级别对框架中的核心类进行了展开，并明确了 Dubbo 框架在实现网络通信模块时所采用的分层架构。Dubbo 在这一维度上的分层架构为我们如何合理规划底层模块的职责和边界提供了很好的参考价值。Dubbo 中关于服务器端和客户端通信的实现过程还是比较复杂的，涉及到更底层的 Channel 等相关的操作，大家可以自己通过阅读源码进行理解。&lt;/p&gt;
&lt;p&gt;在本讲内容中，我们还留着一个伏笔，那就是序列化。任何一次网络请求过程都涉及到对数据的序列化操作。而序列化的具体实现工具很多，但这些工具背后实际上还是有一些通用的功能特性值得进行分析和总结，从而方便我们做出技术选型。下一讲我们将针对这个话题展开详细讨论。&lt;/p&gt;
&lt;h1 id=&quot;序列化：如何对序列化实现工具进行正确选型？&quot;&gt;&lt;a href=&quot;#%E5%BA%8F%E5%88%97%E5%8C%96%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AF%B9%E5%BA%8F%E5%88%97%E5%8C%96%E5%AE%9E%E7%8E%B0%E5%B7%A5%E5%85%B7%E8%BF%9B%E8%A1%8C%E6%AD%A3%E7%A1%AE%E9%80%89%E5%9E%8B%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;序列化：如何对序列化实现工具进行正确选型？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们对远程过程调用中客户端与服务器端的网络通信过程进行了详细的讨论。借助于分布式服务框架，我们可以实现不同服务之间跨网络的交互和协作。网络通信涉及到数据的有效传输，这就需要引入另一个技术组件，即序列化。而目前关于如何实现序列化和反序列化，业界也诞生了一大批工具和框架。&lt;/p&gt;
&lt;p&gt;那么，序列化是一种什么样的技术组件？我们又应该如何对种类繁多的序列化实现工具进行正确选型呢？这是面试官经常会抛出的一个面试话题。本讲内容将围绕这一话题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在现实中，我们通常会使用 Dubbo、Spring Cloud 等开源框架，以及 TCP、HTTP 等网络传输协议来发起远程调用。对于这些框架和协议而言，客户端发起请求的方式，以及服务端返回响应结果的处理过程都是不一样的。但是这里存在一个共同点，即无论采用何种开发框架和网络传输协议，都涉及到业务数据在网络中的传输，这就需要应用到序列化技术。和上一讲介绍到的网络通信不同，序列化技术是直接面向开发人员的，我们可以对具体的序列化工具和框架进行选择，而不像网络通信过程那样只能依靠框架底层所封装的能力。对于序列化技术而言，这种差异性导致了面试过程不仅仅只会考查对技术本身的了解程度，而是更多会讨论如何对序列化工具进行合理选择。&lt;/p&gt;
&lt;p&gt;目前，序列化工具很多，据统计已经不下 100 种。在面试过程中显然无法对具体的实现工具进行一一罗列。因此，关于序列化技术的考查方式是比较灵活的，需要候选人有足够的知识面，了解目前业界主流的序列化技术。更为重要的，候选人还需要具备综合的抽象思维，能够将不同的工具按照一定的维度进行分类，从不同的功能特性角度出发进行分析。&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;为什么像 Protobuf、Thrift 这些序列化工具会采用中间语言技术？&lt;/li&gt;
&lt;li&gt;如果只考虑性能，你会选择哪款序列化工具？&lt;/li&gt;
&lt;li&gt;Google 的 Protobuf 为什么会那么快？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到，这些面试题既包括对序列化技术原理和实现方法的考查，也会涉及到具体某一个工具框架的细节。从我的面试经验而言，能够回答比较顺畅的候选人并不是很多，需要大家在平时的工作中不断积累。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-1&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;究竟什么是序列化？我们可以简单把它理解为是一种从内存对象到字节数据的转换过程。通过序列化，我们就可以把数据变成可以在网络上进行传输的一种媒介，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-16-11-52-42-d67df193e7a1c32d91679e56fb2affb3-f0556.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 596px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 37.41610738255034%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABS0lEQVQoz3VRXU/CQBDs//8fBjUaHo08KJJAAorQlhZogZ5AOUpbgWLTD2nH7YUmKHGTub3c3sztzkk4RZ7n+Buu62Axn8GyTJjmEIuFhY3DL+4V3JIv5XmGElmWIY4jpGlKyGAYTXibGoxxFd3XK6z5A0bDuiCmaYIoipAkCc41pMuuPDDGsN8HaLcbJNAiwTYUuQHToKx0RM22bTiOC86dX3zJdTmJLAk2fH+F1YoRbIRhSK+nKCaJohRB8CUIx2MmavP5ByYTHdvP9Ym/hOdxSN23W0zMKuR+BXKvQl3cYzodlO6INY5jHA4HsS9sKYKxMbTBDYbaHVT5GqpSwWz6CMkYd2mcF/R7dXQ6NehaE7udfzI7+1cwCLbQ9Zbg9d6fCU9glgIpDCPyZYDRyIRM2fd3Fz93LliclaK2zdGXVSiqRuPPyKJv/ACTHQ0Prv+kQgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 16 11 52 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-16-11-52-42-d67df193e7a1c32d91679e56fb2affb3-f0556.png&quot; data-srcset=&quot;/static/2024-10-16-11-52-42-d67df193e7a1c32d91679e56fb2affb3-ae33b.png 200w,
/static/2024-10-16-11-52-42-d67df193e7a1c32d91679e56fb2affb3-daf23.png 400w,
/static/2024-10-16-11-52-42-d67df193e7a1c32d91679e56fb2affb3-f0556.png 596w&quot; data-sizes=&quot;(max-width: 596px) 100vw, 596px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们注意到还有一个反序列化的概念。所谓反序列化，实际上就是序列化的逆向过程，把从网络上获取的字节数据再次转化为可以供内存使用的业务对象。&lt;/p&gt;
&lt;p&gt;序列化的方式有很多，实现工具也非常丰富，常见的如下表所示：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;序列化工具&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;简要描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Java Serializable&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;JDK 自带序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Hessian&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Dubbo 框架默认序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Protobuf&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;gRPC 框架默认序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Thrift&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Facebook 跨语言序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Jackson&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Spring 框架默认序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;FastJson&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;阿里巴巴高性能序列化工具&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h2 id=&quot;技术体系-3&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;序列化工具和框架各有特色，但它们所采用的数据表现形式一般分成两大类，即具备可读性的文本类，以及不具备可读性的二进制类。对于前者，代表性的框架有 Jackson 和 FastJson，它们都采用了 JSON 作为数据表现形式。而后者则包括 Protobuf、Thrift 等。开发人员往往对序列化工具的数据表现形式比较在意，因为这直接决定了我们是否可以直接人为对数据的正确性进行判断。显然，数据的表现形式是序列化工具的一大功能特性，但并不是最重要的功能特性。接下来，就让我们先从序列化的核心功能开始展开讨论。&lt;/p&gt;
&lt;h3 id=&quot;序列化的功能&quot;&gt;&lt;a href=&quot;#%E5%BA%8F%E5%88%97%E5%8C%96%E7%9A%84%E5%8A%9F%E8%83%BD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;序列化的功能&lt;/h3&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;我们首先来看数据结构，这方面的功能差异性主要体现在是否对一些复杂数据结构的支持。常见的复杂数据结构包括泛型结构以及 Map/List 等集合结构。我们来看如下所示的一个具体的示例：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;6572847415957117000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class User {
    public int id;
    public String username;
    public List&lt;Link&gt; links;
    public Map result;
}

public class Link {
    public String name;
    public String phone;
}`, `6572847415957117000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Link&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; phone&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里定义了一个 User 对象，而这个 User 对象中又分别包含了一个 List 结构和一个 Map 结构，其中 List 结构所指向的还是一组自定义对象 Link。针对这种复杂数据结构，如果我们使用 FastJson 来进行序列化，那么如下所示的代码是可以正常运作的。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26327008587403444000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`User user = new User();
// 省略对 user 对象进行赋值
// 将对象转化为 JSON 字符串
String str = JSON.toJSONString(user);
// 将 JSON 字符串转成对象
User myuser = JSON.parseObject(str, User.class);`, `26327008587403444000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 省略对 user 对象进行赋值&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 将对象转化为 JSON 字符串&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; JSON&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJSONString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 将 JSON 字符串转成对象&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; myuser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; JSON&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而如果我们使用 Protobuf，那么这种复杂数据结构是无法支持的。如果想要使用 Protobuf，需要对这个数据结构进行调整，将 List 换成更为通用的数据结构。另一方面，针对 Protobuf 框架，我们也可以引出下一个我们要讨论的功能点，即开发的友好性。&lt;/p&gt;
&lt;p&gt;开发友好性用来衡量工具本身对于开发人员实现序列化的开发难易程度。就像前面示例所展示的，我们在使用 FastJson 时，通过几行代码就能实现对象的序列化和反序列化。而有些工具则不一定，以 Protobuf 为例，在使用该工具时，我们首先要做的是定义一种中间语言，示例如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58215816221886810000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`syntax = &amp;quot;proto3&amp;quot;;

message Student
{
    int32 id= 1;
    string name = 2;
    int32 sex = 3;
    string hobby = 4;
    string skill = 5;
}`, `58215816221886810000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;protobuf&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;protobuf&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;protobuf&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-protobuf line-numbers&quot;&gt;&lt;code class=&quot;language-protobuf&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;syntax&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;proto3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Student&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;int32&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;int32&lt;/span&gt; sex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; hobby &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; skill &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code class=&quot;language-text&quot;&gt;syntax=&amp;quot;proto3&amp;quot;&lt;/code&gt; 表示运用 proto3 版本的语法，而 message 类似于 Java 中的 Class。在开发过程中，我们需要将这段中间语言保存为一个 student.proto 文件，然后再通过 Protobuf 的 protoc 命令将它转化为 Java 文件才能使用，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;47336854520589025000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protoc --java_out=. student.proto`, `47336854520589025000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;protoc --java_out&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;. student.proto&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，中间语言所带来的开发复杂度是显而易见的。使用中间语言的典型工具还包括 Thrift，它需要事先提供一个 .thrift 文件。&lt;/p&gt;
&lt;p&gt;讲到这里，你可能会问，为什么 Protobuf 和 Thrift 要使用中间语言呢？原因就在于它们基于中间语言提供了一项重要的技术特性，即跨语言的异构性。&lt;/p&gt;
&lt;p&gt;异构性的需求来自分布式系统中技术架构和实现方式的多样性。原则上，每个服务都可以基于不同的技术体系进行实现，这些技术体系所采用的开发语言可能都是不一样的，这时候就需要引入支持多语言的序列化工具，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-16-11-57-19-691b9053c54ee4b876e2d256040bec7c-94c27.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 616px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.07792207792208%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABX0lEQVQoz31S+0+CUBj1//8jyt+q2Vbz1cqGzWTgA13KK3nY2kJq6hIBE8TTvYimGX3scrh35zt85+xm8KvW63WMk8kHhFYO+qCMJn+OrnAJVSlioBYgS42EG9H3rodW5lgwinE8tiH2r2C9Magyp3ioZqHIBbKvQJb5HXdfLEVwQ4iiCPP5HK7rEnTgeS5838NsNkMQBAfcfyfcJywWSyKygGXZ0DQj3nuef8CjT+qEMSERc10fz2oNpnGH3lMeQptmeItu55rk+37E3wluc9hgFFul5TguFOUe2qBAhEqQxDzarQuyzjCd2kksq13fVuNPy4kmsalDVVWMRiNi28JwaEKS5DiGtMpIIkts8dA1LkbTaJBpGIgin9qkKG1yAxjyQ47EwsZo6Dw5Y5HpCDmswhaGZhH12gleX8pYfnEktxLC8CcGuug3Pev3buB7dTifj+DYLOxRBWHQBNX6Bi/zqBWKLxeEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 16 11 57 19&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-16-11-57-19-691b9053c54ee4b876e2d256040bec7c-94c27.png&quot; data-srcset=&quot;/static/2024-10-16-11-57-19-691b9053c54ee4b876e2d256040bec7c-64932.png 200w,
/static/2024-10-16-11-57-19-691b9053c54ee4b876e2d256040bec7c-2fd6e.png 400w,
/static/2024-10-16-11-57-19-691b9053c54ee4b876e2d256040bec7c-94c27.png 616w&quot; data-sizes=&quot;(max-width: 616px) 100vw, 616px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;实际上，很多场景下我们之所以不选择 JDK 自带的序列化机制，很重要的一个原因就在于它只能支持 Java 语言。而基于 Protobuf 等工具所提供的中间语言机制，我们可以生成面向不同语言的序列化数据，包括 C++、JAVA、Python、Objective C、C#、Ruby、PHP、JavaScript 等，我们也可以找到几乎涵盖所有其他语言的第三方拓展包。&lt;/p&gt;
&lt;p&gt;另一方面，无论是数据结构的丰富程度，还是开发友好性，这些功能特性与跨语言支持之间往往是存在一定矛盾的。举个例子，要想支持多语言，那么就必须采用那些非常通用的数据结构，确保所有语言都内置了对这些数据结构的支持。这样的话，诸如前面提到的 Map/List 等复杂数据结构显然就不合适了。&lt;/p&gt;
&lt;h3 id=&quot;序列化的性能&quot;&gt;&lt;a href=&quot;#%E5%BA%8F%E5%88%97%E5%8C%96%E7%9A%84%E6%80%A7%E8%83%BD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;序列化的性能&lt;/h3&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;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;时间复杂度（序列化）&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;时间复杂度（反序列化）&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;空间复杂度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Java&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;8654&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;43787&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;889&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Hessian&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;6725&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;10460&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;501&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Protobuf&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2964&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1745&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;239&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Thrift&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;3177&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1949&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Jackson&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;3052&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;4161&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;503&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Fastjson&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2595&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1472&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;468&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;通过对比，我们注意到在时间复杂度上可以优先选择阿里巴巴的 FastJson，而在空间复杂度上 Google 的 Protobuf 则具备较大的优势。&lt;/p&gt;
&lt;h3 id=&quot;序列化的兼容性&quot;&gt;&lt;a href=&quot;#%E5%BA%8F%E5%88%97%E5%8C%96%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;序列化的兼容性&lt;/h3&gt;
&lt;p&gt;关于序列化技术最后需要讨论的一个话题是兼容性。&lt;/p&gt;
&lt;p&gt;我们知道随着业务系统的不断演进，服务中所定义的接口以及数据结构也不可避免会发生变化。通常，在分布式服务开发过程中，我们会引入版本概念来应对接口和数据结构的调整。在序列化工具中，我们同样需要考虑版本。这方面比较典型的例子就是 JDK 自带的序列化版本 Id。一旦我们在类定义中实现了 Serializable 接口，JDK 就提示你需要指定一个序列化版本 Id，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22822316248061280000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class MyObject implements Serializable {
    // 唯一的序列化版本号
    private static final long serialVersionUID = -1127800498182345096L;
}`, `22822316248061280000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyObject&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 唯一的序列化版本号&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; serialVersionUID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1127800498182345096L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;基于这种显式的版本号机制，在序列化时，如果对象之间的版本 Id 不一致，那么 JVM 就会抛出一个 InvalidCastException 的异常；反之则可以正常进行转换。&lt;/p&gt;
&lt;p&gt;有些序列化工具虽然没有明确指定版本号的概念，但也能实现前向兼容性，比较典型的就是 Protobuf。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-1&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;介绍完序列化的相关技术体系，让我们再次回到具体的分布式开源框架，来看看业界主流的框架是如何实现序列化过程的。这里还是以阿里巴巴的 Dubbo 框架为例展开讨论。&lt;/p&gt;
&lt;p&gt;在上一讲中，我们介绍了 Dubbo 框架中的 Remoting 模块。作为回顾，我们给出该模块的组成结构图，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-16-14-34-22-12cc3b13f1db81a7e75dc3bf03a93527-b2673.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 620px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 16.451612903225808%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAA0ElEQVQI1yXNyU7CUACF4b7/W4gxIAE3MrVhoREELHQekJbetgpSh8jeW36vsjibc3LyaVJK6rpGyh9OJyjLNa7dxVp2iEKd3W7P5vmO6vBAujEI/FvSxCCOBoRBjyjos46HHN4mvL7cowX+GM/t/Y9p4jKdGHhOk6V5pU59tpnA99Tut5lNL7BXTSyVxazBYn7J/LGBY7WIwxsKMUD7+tzz8V5SVTnH4zdJYiugw8psKWCMEIXCRuRiSLYdUeQ65tMfeI3rdBHZucuFTlkY/AIIGM6+rMFaqwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 16 14 34 22&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-16-14-34-22-12cc3b13f1db81a7e75dc3bf03a93527-b2673.png&quot; data-srcset=&quot;/static/2024-10-16-14-34-22-12cc3b13f1db81a7e75dc3bf03a93527-fcc72.png 200w,
/static/2024-10-16-14-34-22-12cc3b13f1db81a7e75dc3bf03a93527-3d8b3.png 400w,
/static/2024-10-16-14-34-22-12cc3b13f1db81a7e75dc3bf03a93527-b2673.png 620w&quot; data-sizes=&quot;(max-width: 620px) 100vw, 620px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在 Remoting 模块中还剩下一个 Serialize 组件没有介绍，从命名上我们不难看出该组件与序列化相关。事实上，Dubbo 提供了 Serialization 接口（位于 dubbo-common 代码工程中）作为对序列化的抽象。而对应的序列化和反序列化操作的返回值分别是 ObjectOutput 和 ObjectInput，其中 ObjectInput 扩展自 DataInput，用于读取对象；而 ObjectOutput 扩展自 DataOutput，用于写入对象，这两个接口的定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;20954996577553863000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ObjectInput extends DataInput {
    Object readObject() throws IOException, ClassNotFoundException;
    &lt;T&gt; T readObject(Class&lt;T&gt; cls) throws IOException, ClassNotFoundException;
    &lt;T&gt; T readObject(Class&lt;T&gt; cls, Type type) throws IOException, ClassNotFoundException;
}

public interface ObjectOutput extends DataOutput {
    void writeObject(Object obj) throws IOException;
}`, `20954996577553863000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectInput&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataInput&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cls&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectOutput&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataOutput&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Serialization 接口的定义上，可以看到 Dubbo 中默认使用的序列化实现方案基于 hessian2。Hessian 是一款优秀的序列化工具。在功能上，它支持基于二级制的数据表示形式，从而能够提供跨语言支持；在性能上，无论时间复杂度还是空间复杂度也比 Java 序列化高效很多，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-16-14-35-05-2f0d5723e482a336f1ed1567a7c3aa67-b2673.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 620px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 27.580645161290324%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABYUlEQVQY0yWRW0/CQBSE+/9/hxqjIom3IJr4gIC00FZa29oWCEGsUm4WULDi52F9mJzdTWbOzKwW+GW8p3/43g1m64wocgnDCM/zWK1W1Ot1fN9nOBxiWSZ5vsVpVwVXirPjPrllXOcaDSy+Nwb5Rmd3BhPbviNNZ0wmY8bjsRJKkkTuE5bLhcw5j/YFP3lDuDr5t8Hv1hSuixYGZ+iNA4VOfMX2pyku7hiNJry/v4nbiE6nw2AwII5jgiDAcTxqtWPi8JyH2h4t45Ao3C0w0Qb9Eq8vtzyL8HLxoFyWSodC7jGdTlXUbrerXPb7fSWeJCNxeMmzX1RCTrvAx7wq3PYusg2/EnttqAd4xDDKIpaRZR/MZjMVNU1TNReLjPU6l85KrL/qqq6vzwZKBw/Ntk5pGgX5jFNBker9vpTcwnU9gSuiGZVKhV6vJ93aErXKZpOj6zey+AjLLCq0mgXROeEPX1SrIg5U0bYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 16 14 35 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-16-14-35-05-2f0d5723e482a336f1ed1567a7c3aa67-b2673.png&quot; data-srcset=&quot;/static/2024-10-16-14-35-05-2f0d5723e482a336f1ed1567a7c3aa67-fcc72.png 200w,
/static/2024-10-16-14-35-05-2f0d5723e482a336f1ed1567a7c3aa67-3d8b3.png 400w,
/static/2024-10-16-14-35-05-2f0d5723e482a336f1ed1567a7c3aa67-b2673.png 620w&quot; data-sizes=&quot;(max-width: 620px) 100vw, 620px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在 Dubbo 中，Hessian2Serialization 类实现了 Serialization 接口，我们就以该类为例介绍 Dubbo 中具体的序列化/反序列化实现方法。Hessian2Serialization 类定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;55090266129459905000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class Hessian2Serialization implements Serialization {
    public static final byte ID = 2;
    public byte getContentTypeId() {
        return ID;
    }

    public String getContentType() {
        return &amp;quot;x-application/hessian2&amp;quot;;
    }

    public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
        return new Hessian2ObjectOutput(out);
    }

    public ObjectInput deserialize(URL url, InputStream is) throws IOException {
        return new Hessian2ObjectInput(is);
    }
}`, `55090266129459905000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2Serialization&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serialization&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt; ID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContentTypeId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ID&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getContentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;x-application/hessian2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectOutput&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OutputStream&lt;/span&gt; out&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2ObjectOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectInput&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deserialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InputStream&lt;/span&gt; is&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2ObjectInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;is&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hessian2Serialization 中的 serialize 和 deserialize 方法分别创建了 Hessian2ObjectOutput 和 Hessian2ObjectInput 类。以 Hessian2ObjectInput 为例，该类使用 Hessian2Input 完成具体的反序列化操作，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;20892511330201470000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class Hessian2ObjectInput implements ObjectInput {
    private final Hessian2Input mH2i;

    public Hessian2ObjectInput(InputStream is) {
        mH2i = new Hessian2Input(is);
        mH2i.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
    }

    // 省略各种读取具体数据类型的工具方法

    public Object readObject() throws IOException {
        return mH2i.readObject();
    }

    public &lt;T&gt; T readObject(Class&lt;T&gt; cls) throws IOException,
            ClassNotFoundException {
        return (T) mH2i.readObject(cls);
    }

    public &lt;T&gt; T readObject(Class&lt;T&gt; cls, Type type) throws IOException, ClassNotFoundException {
        return readObject(cls);
    }
}`, `20892511330201470000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2ObjectInput&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectInput&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2Input&lt;/span&gt; mH2i&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2ObjectInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InputStream&lt;/span&gt; is&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        mH2i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hessian2Input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;is&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        mH2i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSerializerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Hessian2SerializerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SERIALIZER_FACTORY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 省略各种读取具体数据类型的工具方法&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; mH2i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; mH2i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cls&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Type&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hessian2Input 是 Hessian2 的实现库 com.caucho.hessian 中的工具类，初始化时需要设置一个 SerializerFactory，所以我们在这里还看到存在一个 Hessian2SerializerFactory 工厂类，专门用于设置 SerializerFactory。而在 Hessian2ObjectInput 中，各种以 read 为前缀的方法实际上都是对 Hessian2Input 中相应方法的封装。&lt;/p&gt;
&lt;p&gt;用于执行反序列化的 Hessian2ObjectOutput 与 Hessian2ObjectInput 类也比较简单，这里不再展开。&lt;/p&gt;
&lt;p&gt;关于 Dubbo 序列化的另一条代码支线是 Codec2 接口，该接口位于 dubbo-remoting-api 代码工程中，提供了对网络编解码的抽象，而编解码过程显然需要依赖 Serialization 接口作为其数据序列化的手段。我们可以通过如下所示的代码片段来回顾这一点，这段代码来自 DubboCodec 中的 decodeBody 方法。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27123503164483285000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class DubboCodec extends ExchangeCodec implements Codec2 {

    protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
        // 获取序列化对象
        Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);
    }
}`, `27123503164483285000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DubboCodec&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExchangeCodec&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Codec2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decodeBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InputStream&lt;/span&gt; is&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; header&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 获取序列化对象&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Serialization&lt;/span&gt; s &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CodecSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSerialization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; proto&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;那么，这里的 Codec 和 Serialization 如何与上一讲中介绍的 Exchange 和 Transport 结合起来构成一个完整的链路呢？我们可以明确一点，序列化和编解码过程在网络传输层和信息交换层中都应该存在。因此，我们快速来到 &lt;code class=&quot;language-text&quot;&gt;dubbo-remoting-api&lt;/code&gt; 代码工程的 &lt;code class=&quot;language-text&quot;&gt;META-INF/dubbo/internal&lt;/code&gt; 文件夹，发现存在一个 org.apache.dubbo.remoting.Codec2 配置文件，内容如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;87412460857546900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`transport=org.apache.dubbo.remoting.transport.codec.TransportCodec
telnet=org.apache.dubbo.remoting.telnet.codec.TelnetCodec
exchange=org.apache.dubbo.remoting.exchange.codec.ExchangeCodec`, `87412460857546900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;transport=org.apache.dubbo.remoting.transport.codec.TransportCodec
telnet=org.apache.dubbo.remoting.telnet.codec.TelnetCodec
exchange=org.apache.dubbo.remoting.exchange.codec.ExchangeCodec&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;org.apache.dubbo.remoting.Codec2 配置文件用来执行 SPI 机制，我们会在之后对这个主题进行专项介绍，这里只需要明白 Dubbo 采用这种配置方式来动态加载运行时的类对象。在这里，可以看到 Dubbo 针对 exchange 和 transport 都提供了 Codec 支持。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-3&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;从解题思路上讲，序列化是一个相对比较容易把握的面试题。基于本讲关于序列化技术组件的讨论，我们发现有很多列表式的内容需要记忆。这部分内容需要大家平时多看一些资料，尽量扩展自己的知识面，这是针对这一主题的第一个解题要点。关于序列化相关工具之间的对比也有一个非常好的&lt;a href=&quot;https://github.com/eishay/jvm-serializers/wiki&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;汇总资料&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;但也正是因为序列化本身是一个内容比较固化的主题，所以在解题上就不能完全照本宣科。只讲概念，而不给出自己的一些思考和总结，往往体现不出你和其他候选人之间的差别，这也是日常面试过程中需要注意的一个点。因此，针对这类题的第二个解题要点在于要事先用自己的语言来梳理回答问题的内容体系，重点展示自己对于这一技术主题的抽象和分析能力。针对技术选型类面试题，更加需要明确给出自己的判断。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-1&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;但凡开发分布式和微服务应用，就一定会使用到序列化技术。可以说，序列化技术是我们开发互联网应用程序的基础设施类技术之一。序列化的实现工具有很多，本讲内容并不是介绍具体的某一个工具，而是从功能、性能以及兼容性等维度出发对这些工具进行分析，从而帮助你更好的进行选择。&lt;/p&gt;
&lt;p&gt;具备了网络通信和序列化技术，接下来我们就可以实现一个简单的远程调用过程了。那么，如果让你自己设计一个简单的 RPC 架构，你会怎么做呢？这就是我们下一讲要探讨的内容。&lt;/p&gt;
&lt;h1 id=&quot;远程调用：如果让你自己设计一个简单的-rpc-架构，你会怎么做？&quot;&gt;&lt;a href=&quot;#%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%AE%A9%E4%BD%A0%E8%87%AA%E5%B7%B1%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-rpc-%E6%9E%B6%E6%9E%84%EF%BC%8C%E4%BD%A0%E4%BC%9A%E6%80%8E%E4%B9%88%E5%81%9A%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;远程调用：如果让你自己设计一个简单的 RPC 架构，你会怎么做？&lt;/h1&gt;
&lt;p&gt;任何分布式系统的开发都涉及到跨进程之间的远程过程调用，也就是所谓的 RPC。前面两讲我们讨论的网络通信和序列化实际上也都属于是 RPC 架构的范畴，只是关注于不同的技术切入点。而 RPC 本身也构成一种架构体系，包含一系列相互协作的核心组件。在 Dubbo、Spring Cloud 等主流的分布式服务框架中，这些技术组件使用起来都非常简单，因为框架为开发人员屏蔽了底层的实现细节。&lt;/p&gt;
&lt;p&gt;那么，现在假如没有这些开源框架，而是需要你自己来设计并实现一套远程过程调用机制，你应该怎么做的？我认为这是一个很好的面试题，考查了候选人的综合技术能力。本讲内容将围绕这个话题展开详细讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-1&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，服务与服务之间需要通过远程调用来完成一个个具体的业务操作。这是一个全流程的执行过程，涉及到前面介绍到的网络通信和序列化技术，也涉及到本讲内容要进一步阐述的远程调用中的不同技术组件。&lt;/p&gt;
&lt;p&gt;在使用 Dubbo、Spring Cloud 等框架时，你可能并没有感受到远程调用过程是如何执行的，因为这些框架都提供了“远程调用本地化”机制，开发人员调用远程方法感觉和调用本地方法并没有什么差别。这是框架所具备的能力，我们直接使用即可。但从面试角度讲，如果只考查框架的使用方式，显然无法真正判断候选人的技术能力。因此，在面试过程中，面试官通常都会暂时抛开框架的具体使用方式，而从远程调用的基本概念和执行流程出发来进行提问。&lt;/p&gt;
&lt;p&gt;针对这类面试题，如果候选人没有做精心的准备，往往很难回答到位，原因就在于我们平时不大会从这一角度入手考虑问题，也就不会重点关注相关的技术体系。从面试角度讲，这道题也存在一些灵活的提问方式，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想要实现远程调用，整个流程应该包含那些基本的技术组件？&lt;/li&gt;
&lt;li&gt;RPC 架构的组成结构是怎么样的？&lt;/li&gt;
&lt;li&gt;如果让你设计一个简单的 RPC 架构，你会怎么做？&lt;/li&gt;
&lt;li&gt;你认为 Dubbo 框架中最核心的组件有哪些？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RPC 这个概念我们一直都在说，但面对这一概念，我们应该如何进行系统化的学习呢？我们接下来对这个问题进行分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-2&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;远程过程调用，英文即为 Remote Procedure Call，也就是我们通常所说的 RPC，这个名词第一次出现是在 1974 年。从诞生到今天，RPC 架构存在了 40 多年，已经演变成一切分布式系统的基础性架构。而围绕 RPC 架构的基本概念，这么多年以来实际上并没有发生大的变化。所以，关于 RPC 架构本身的学习内容和范围是相对固定的，这是我们首先需要明确的一点。&lt;/p&gt;
&lt;p&gt;从面试角度讲，RPC 架构最应该掌握的是它的基本组成结构。作为一种架构模式，业界已经对 RPC 架构的各个组成部分进行了抽象和提炼，从而形成一套完整的组件体系。正是基于这套 RPC 的组件体系，业界诞生了各种具体实现框架，Dubbo 就是其中的代表。而无论 Dubbo 等框架如何实现，其底层的组成结构是完全遵循 RPC 架构的。因此，只要掌握了 RPC 架构的组成结构，关于这类问题的大部分内容我们已经可以回答了。&lt;/p&gt;
&lt;p&gt;事实上，基于 RPC 的组成结构，任何人都可以自己实现一套 RPC 框架。我们可以采用最简单、最常见的技术体系实现一个最基础的 RPC 架构。通过这一过程，一方面可以确保具体的实现技术和纯粹的理论体系能够对应起来；另一方面，在面试过程中，这也是凸显出个人优势的一个加分项，能够提升面试官对候选人的认可程度。&lt;/p&gt;
&lt;p&gt;好了，讲到这里，相信你已经具备了针对这类问题的解答思路。接下来要讨论的就是具体的技术体系了，让我们从 RPC 架构的组成结构开始讲起。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-4&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;如果我们站在最高的层次来看 RPC 的执行流程，就是一个服务的消费者向服务的提供者发起远程调用并获取返回结果的过程，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-17-10-34-44-c63aaaa80662443f601466425b622445-f1382.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 533px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 19.69981238273921%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAA0ElEQVQY032P206DUBBF+f9fMdISrzH+gBKgHKsBirTSpp62CZTCE7flgLE+6SSTyc7sNReDf6Lve5qmIQgCwjCkKIqfzp+MEUUO63RGKjnUzdonXtgcj9nZVNc1VVWd9bAkSXzxeyPzzXsk7w7G2+sU/XkvwiKJp3wsrzjsHlDqidOpIssytNYCpOR5TlmWuK7HyG3vWAoXRybbzS07/YhhP1sof4LrXOK5Jv5swouyWK0W8vLvdcPgtm3puo79/iDeG/GaI+PYF8yFmatrvgBqlSYZbA+6IAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 17 10 34 44&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-17-10-34-44-c63aaaa80662443f601466425b622445-f1382.png&quot; data-srcset=&quot;/static/2024-10-17-10-34-44-c63aaaa80662443f601466425b622445-7cec2.png 200w,
/static/2024-10-17-10-34-44-c63aaaa80662443f601466425b622445-dfa51.png 400w,
/static/2024-10-17-10-34-44-c63aaaa80662443f601466425b622445-f1382.png 533w&quot; data-sizes=&quot;(max-width: 533px) 100vw, 533px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;接下来，如果我们把上图做一些展开。通过之前的学习，我们知道服务提供者需要暴露服务访问的入口，而消费者则会向提供者所暴露的访问入口发起请求。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-17-10-35-20-492d8de3aca701cb6350cbc666ab2535-e07da.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 611px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 15.548281505728315%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAuElEQVQI1x2O3QqCQBCFff8X6S4ydVWKAhW6qyhy/df1f1fBRzjNejEw83HOxxhNHWBRIU0E0VyRpS6K3IOUIZSMMPRXbNuIZekwjXfIWfNwz+is7uickgH6/gEj4QxN5aCuGMrcoqCFNDGJEa9ddK2DdRUYh4Lk3s4ETVU4yLMzysJGXTLK6UcuMHjsEnDo0FIX83SjkkcFhiyx8X4eoFRDwhIv2r+fI/jvtItb4ZPUBo/NXZ5wH3/SAdDEWRMD0AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 17 10 35 20&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-17-10-35-20-492d8de3aca701cb6350cbc666ab2535-e07da.png&quot; data-srcset=&quot;/static/2024-10-17-10-35-20-492d8de3aca701cb6350cbc666ab2535-20936.png 200w,
/static/2024-10-17-10-35-20-492d8de3aca701cb6350cbc666ab2535-da2be.png 400w,
/static/2024-10-17-10-35-20-492d8de3aca701cb6350cbc666ab2535-e07da.png 611w&quot; data-sizes=&quot;(max-width: 611px) 100vw, 611px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，这里我们对服务消费者和提供者的组成结构做了细化，并提取了 RpcChannel、RpcProtocol、RpcConnector 和 RpcAcceptor 这四个技术组件。在这四个技术组件中，前两个属于公共组件，而后两个则面向服务的提供者和服务的消费者，分别用于发起请求和接收响应。&lt;/p&gt;
&lt;p&gt;有了底层的用于完成网络通信的技术组件之后，我们再来看如何从业务接口定义和使用的角度出发进一步对 RPC 架构的组成结构进行扩充，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-17-10-36-09-dbc50f807b3779a34d824c21dc685611-e07da.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 611px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40.098199672667754%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABwUlEQVQoz11Sj2+aYBDl//8/ljW1tp2rxFnnFLG1S/0FIiIgoKCgFAETY3+sb0+TNdu+5HJ897179+4OIQqnsMwqtFEFnldHFDmwLA1jrQx9fIP12sWfE4YOY2WY0wocR4frGvD9JoaDMpxZDaulDmG3y7BcOjBNjaYiSZ6QpimBDsbjIbbb5IMwy1JoWp9kBvJdTlzCHAWGoWCzniPPEwjO7AG23cSgL7L6LQL/Hv7iDhO9zsoi1dSYGNE2VFGHrlfpJeLaiMLO6X7ELRYy5t5PCNqoBGNyRcJz2OZXgmqYuyKU4TnHcEnCIlYrm+26mNklqEqRbwV4jkjSb4yV0X38jKlxRZ4KhMCXEK1+kL2KJG7h9aWLl+cu0m2bxLdInhr8DjiKJfJMhneMxTKeD4/E9hBvJDh2BfG6ybYfIKjKF6hUowwKXESRCyqdvKpcsJUC45+oesZleRxJAUN2MlIvqLyE6eSaqq7R753B0C/ZvgjBtjqULcG2WvQtttFhRYnEDSY1OIobLiZElsYs9J0ETb7JnOMdFnPO35JpbeYe1fcg4L/z/v7v/e3tF4Ig4C/lccu7D8zfdjgcsN/vT/43tNc8NIXmReEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 17 10 36 09&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-17-10-36-09-dbc50f807b3779a34d824c21dc685611-e07da.png&quot; data-srcset=&quot;/static/2024-10-17-10-36-09-dbc50f807b3779a34d824c21dc685611-20936.png 200w,
/static/2024-10-17-10-36-09-dbc50f807b3779a34d824c21dc685611-da2be.png 400w,
/static/2024-10-17-10-36-09-dbc50f807b3779a34d824c21dc685611-e07da.png 611w&quot; data-sizes=&quot;(max-width: 611px) 100vw, 611px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中出现了用于代表业务接口的 API 组件，同时，我们也看到了分别代表客户端和服务器的 RpcClient 和 RpcServer 组件。我们不难想象这些组件的定位和作用。而这里需要重点介绍的是 RpcCaller 组件和 RpcInvoker 组件。RpcCaller 组件位于服务消费者端，会根据 API 的定义信息发起请求。而 RpcInvoker 组件位于服务提供者端，负责对 RpcServer 执行具体的调用过程并返回结果。&lt;/p&gt;
&lt;p&gt;最后，为了对远程调用过程进行更好的控制，我们还会引入两个技术组件，分别是 RpcProxy 和 RpcProcessor。完整的 RPC 架构组成结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-17-10-36-47-ba5c9299ad5e1f569f670d2471258ec0-4c6f5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 607px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.764415156507404%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACLElEQVQozzVSZ3PaUBDU//8X+ZSJQzWDDXiwA9hJKAIhQHREk2iidyGxWT0mmrl575127/aKZFkDjMc5jIYy5nMFljWCYfRRyD+hoYXQ62Zxu93hfYfDDvmcH3UtSH8Kk+kco1EJxljGcJjHdKpCWq9NmGaJoC8MBzksLQOrlYVO+xdM45P/NNwf8XC9XlGtJJk8zcQa1us9xVTR7WSJz2K9akPabJaYTgw0G3UoShGLuQFrYRLYR1HOUUkD5/OBQe9YLk00myo0TWEVOrbbKZV1Ua0q6PXamM9MSHLBh4r6A7+/vqHVCEHvRYVVVB/Kig8DPSIUnM9nFAtsQz1IlX60m2GWmmDCCFTlCSX5O/p6FFJff8NqmabKD54ZHA9/sd/94fudgV+x27K8RQ37/R6jwRuWixSriQjs6eiVmcHEfBf47foL0nz2ye5UaWWairtbwniUQLMepko/+xgTAzudTlDLAejdF/YrSjUxIQSoCN7DypCmk4y4eIFcp8RTEVk9glYLcUgfmM0qnPARrWYUg36cgf0cYFxUgrsiuB7P40vlEteg5mdffGi3wuh1IiSGOPUAFf6kmgAVyzgej6iUfexzALWqj7hnDPsvgqvRPJ/oYaOeZrBXkmN0xtm3FAMm6YvzneDeBVlyDbZtQ5ZjfD8TGxem6xkOKUnFnuqYuEuue4fjOHBdV5y3myPO/77L5cLVGHItetzPjdhJx3GF2baHf9w9nm3f8A+1Ws1ekaq63QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 17 10 36 47&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-17-10-36-47-ba5c9299ad5e1f569f670d2471258ec0-4c6f5.png&quot; data-srcset=&quot;/static/2024-10-17-10-36-47-ba5c9299ad5e1f569f670d2471258ec0-3bd24.png 200w,
/static/2024-10-17-10-36-47-ba5c9299ad5e1f569f670d2471258ec0-52f0c.png 400w,
/static/2024-10-17-10-36-47-ba5c9299ad5e1f569f670d2471258ec0-4c6f5.png 607w&quot; data-sizes=&quot;(max-width: 607px) 100vw, 607px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从命名上看，位于服务消费者端的 RpcProxy 组件充当了一种代理机制，确保服务消费者能够像调用本地方法一样调用远程服务。而位于服务提供者端的 RpcProcessor 的作用则是为远程调用执行过程添加各种辅助性支持，包括线程管理、超时控制等。&lt;/p&gt;
&lt;p&gt;这样，我们对整个 RPC 架构的演进过程做了详细的描述。通过对上图中的技术组件做进一步梳理，我们会发现这些组件可以归为三大类，即客户端组件、服务端组件和公共组件。&lt;/p&gt;
&lt;p&gt;其中，客户端组件与职责包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RpcClient，负责导入远程接口代理实现；&lt;/li&gt;
&lt;li&gt;RpcProxy，远程接口的代理实现；&lt;/li&gt;
&lt;li&gt;RpcCaller，负责执行远程调用；&lt;/li&gt;
&lt;li&gt;RpcConnector，负责连接服务器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;服务端组件与职责包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RpcServer，负责导出远程接口；&lt;/li&gt;
&lt;li&gt;RpcInvoker，负责调用服务端接口；&lt;/li&gt;
&lt;li&gt;RpcAcceptor，负责接收网络请求；&lt;/li&gt;
&lt;li&gt;RpcProcessor，负责处理调用过程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而客户端和服务器端所共有的组件包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RpcProtocol，负责执行网络传输；&lt;/li&gt;
&lt;li&gt;RpcChannel，数据传输的通道。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关于 RPC 架构的组成结构介绍到这里就结束了。在这一组成结构的基础上，如果采用合适的编程语言和实现技术，原则上我们就可以自己动手实现一个 RPC 架构。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-2&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;接下来我们通过一个简单的示例来实现前面所介绍的的各个技术组件。该示例的主要目的是演示如何从零开始构建一个最基本的 RPC 架构。&lt;/p&gt;
&lt;p&gt;首先我们定义一个业务服务，称为 UserService，包含一个用于根据用户编码获取用户姓名的业务方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11007160174921316000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface UserService {
    // 根据用户编码获取用户姓名
    public String getUserNameByCode(String userCode);
}`, `11007160174921316000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 根据用户编码获取用户姓名&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUserNameByCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;UserService 接口的实现类也非常简单，我们通过一个内存 Map 来模拟对数据的存储和查询操作，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13373201125505420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class UserServiceImpl implements UserService {

    private Map&lt;String, String&gt; users = new HashMap&lt;String, String&gt;();

    public UserServiceImpl() {
        users.put(&amp;quot;user1&amp;quot;, &amp;quot;tianyalan1&amp;quot;);
        users.put(&amp;quot;user2&amp;quot;, &amp;quot;tianyalan2&amp;quot;);
    }

    @Override
    public String getUserNameByCode(String userCode) {
        return users.get(userCode);
    }
}`, `13373201125505420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceImpl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tianyalan1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tianyalan2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUserNameByCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于 RPC 架构而言，有了服务定义之后，我们就需要分别构建一个服务端组件 RpcServer 和一个客户端组件 RpcClient。但在这之前，我们首先需要定义一种在客户端和服务器端之间进行通信的消息格式，这里命名为 Protocol，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28150245444879098000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class Protocol implements Serializable {
   // 包名 + 接口名称
   private String interfaceName;
   // 调用方法名
   private String methodName;
   // 参数类型：按照接口参数顺序
   private Class[] paramsTypes;
   // 参数：按照接口参数顺序
   private Object[] parameters;

   public Protocol (String interfaceName, String methodName, Class[] paramsTypes, Object[] parameters) {
      super();
      this.interfaceName = interfaceName;
      this.methodName = methodName;
      this.paramsTypes = paramsTypes;
      this.parameters = parameters;
   }
   // 省略 getter/setter 方法
}`, `28150245444879098000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 包名 + 接口名称&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; interfaceName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 调用方法名&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 参数类型：按照接口参数顺序&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; paramsTypes&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 参数：按照接口参数顺序&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; interfaceName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; paramsTypes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;interfaceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; interfaceName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;methodName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paramsTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; paramsTypes&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parameters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 省略 getter/setter 方法&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到 Protocol 中定义了一次服务请求所需要的接口名、方法名以及方法调用所需要的参数。注意到该类同时实现了 Serializable 接口，这是 Java 中的序列化接口，实现该接口的类能够通过网络进行远程传输。在 RPC 基础架构图中，Protocol 类相当于是通过 RpcProtocol 进行传递的请求数据。&lt;/p&gt;
&lt;p&gt;接下来我们考虑构建 RpcServer 类，该类需要实现 RPC 基础架构图中的各个服务端组件。RpcServer 类完整代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43139984080902380000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class RpcServer {
    // 线程池
    private int threadSize = 10;
    private ExecutorService threadPool;
    // 自定义缓存
    private Map&lt;String, Object&gt; servicePool;
    // 服务端口
    private int port = 9000;

    public RpcServer() {
        super();
        synchronized (this) {
            threadPool = Executors.newFixedThreadPool(this.threadSize);
        }
    }

    public RpcServer(int threadSize, int port) {
        this.threadSize = threadSize;
        this.port = port;
        synchronized (this) {
            threadPool = Executors.newFixedThreadPool(this.threadSize);
        }
    }

    public RpcServer(Map&lt;String, Object&gt; servicePool, int threadSize, int port) {
        this.threadSize = threadSize;
        this.port = port;
        this.servicePool = servicePool;
        synchronized (this) {
            threadPool = Executors.newFixedThreadPool(this.threadSize);
        }
    }

    // 1. 实现 Socket 监听：RpcAcceptor
    public void service() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            Socket receiveSocket = serverSocket.accept();
            final Socket socket = receiveSocket;

            // 执行请求
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 2. 处理请求
                        process(socket);

                        socket.close();
                    } catch(IOException e) {
                        // 篇幅关系，省略对各种异常信息的处理
                    }
                }
            });
        }
    }

    // 2.处理请求：RpcProcessor
    private void process(Socket receiveSocket) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        ObjectInputStream objectInputStream = new ObjectInputStream(receiveSocket.getInputStream());

        Protocol protocol = (Protocol)objectInputStream.readObject();

        // 调用服务
        Object result = call(protocol);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(receiveSocket.getOutputStream());
        objectOutputStream.writeObject(result);
        objectOutputStream.close();
    }

    // 3.执行方法调用：RpcInvoker
    private Object call(Protocol protocol) throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InstantiationException, InvocationTargetException {
        if(servicePool == null) {
            synchronized (this) {
                servicePool = new HashMap&lt;String, Object&gt;();
            }
        }

        // 通过接口名称构建实现类
        String interfaceName = protocol.getInterfaceName();
        Object service = servicePool.get(interfaceName);
        Class&lt;?&gt; serviceClass = Class.forName(interfaceName);

        // 判断 servicePool 对象是否存在，如果不存在，就创建新对象并放入池中
        if(service == null) {
            synchronized (this) {
                service = serviceClass.newInstance();
                servicePool.put(interfaceName, service);
            }
        }

        // 通过实现类来构建方法
        Method method = serviceClass.getMethod(protocol.getMethodName(), protocol.getParamsTypes());

        // 通过反射来实现方法的执行
        Object result = method.invoke(service, protocol.getParameters());
        return result;
    }
}`, `43139984080902380000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 线程池&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; threadSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutorService&lt;/span&gt; threadPool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 自定义缓存&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; servicePool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 服务端口&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            threadPool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;threadSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; threadSize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;threadSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; threadSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            threadPool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;threadSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; servicePool&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; threadSize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;threadSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; threadSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;servicePool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; servicePool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            threadPool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;threadSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1. 实现 Socket 监听：RpcAcceptor&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ServerSocket&lt;/span&gt; serverSocket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServerSocket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;port&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Socket&lt;/span&gt; receiveSocket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverSocket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Socket&lt;/span&gt; socket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; receiveSocket&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 执行请求&lt;/span&gt;
            threadPool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 2. 处理请求&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                        socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 篇幅关系，省略对各种异常信息的处理&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2.处理请求：RpcProcessor&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Socket&lt;/span&gt; receiveSocket&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationTargetException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NoSuchMethodException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantiationException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalAccessException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ObjectInputStream&lt;/span&gt; objectInputStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiveSocket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;objectInputStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 调用服务&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;ObjectOutputStream&lt;/span&gt; objectOutputStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectOutputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;receiveSocket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOutputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        objectOutputStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        objectOutputStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3.执行方法调用：RpcInvoker&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NoSuchMethodException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;IllegalAccessException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InstantiationException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationTargetException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;servicePool &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                servicePool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 通过接口名称构建实现类&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; interfaceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInterfaceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; servicePool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; serviceClass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 判断 servicePool 对象是否存在，如果不存在，就创建新对象并放入池中&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serviceClass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                servicePool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 通过实现类来构建方法&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serviceClass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParamsTypes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 通过反射来实现方法的执行&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RpcServer 类代码相对比较长，我们结合 RPC 基本架构对其进行分段解析。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;service 方法&lt;/p&gt;
&lt;p&gt;service 方法接收请求并基于 Socket 启动端口监听，通过线程池为进入的每个请求启动一个线程进行处理。就 RPC 基础架构而言，该 service 方法相当于扮演 RpcAcceptor 的角色。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;process 方法&lt;/p&gt;
&lt;p&gt;service 方法启动了线程池，而每个线程负责执行 process 方法。这里的 process 方法从 Socket 中获取输入流，然后把输入流中的数据转化为 Protocol，从而获取远程方法调用的各项元数据。就 RPC 基础架构而言，该 process 方法充当了 RpcProcessor 的角色。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;call 方法 一旦获取 Protocol，process 方法就调用内部的 call 方法来执行真正的方法调用。这里通过反射机制获取位于服务器端的方法并进行执行。显然，该 call 方法对应于 RpcInvoker 角色。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;介绍完 RpcServer 中的各个技术组件，我们再来看一下 RpcClient 的代码，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;36890466917962207000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class RpcClient {
    private String serverAddress;
    private int serverPort;

    public RpcClient(String serverAddress, int serverPort) {
         this.serverAddress = serverAddress;
         this.serverPort = serverPort;
    }

    // RpcConnector + RpcCaller
    public Object sendAndReceive(Protocol protocol) {
         Object result = null;

         try {
            Socket socket = new Socket(serverAddress, serverPort);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(protocol);

            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            result = objectInputStream.readObject();
         } catch (Exception e) {
            // 篇幅关系，省略对各种异常信息的处理
         }

         return result;
    }
    // 省略 getter/setter 方法
}`, `36890466917962207000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serverAddress&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; serverPort&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serverAddress&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; serverPort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serverAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverAddress&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serverPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverPort&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// RpcConnector + RpcCaller&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendAndReceive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Socket&lt;/span&gt; socket &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Socket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serverAddress&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; serverPort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;ObjectOutputStream&lt;/span&gt; objectOutputStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectOutputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOutputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            objectOutputStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token class-name&quot;&gt;ObjectInputStream&lt;/span&gt; objectInputStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; objectInputStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 篇幅关系，省略对各种异常信息的处理&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 省略 getter/setter 方法&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RpcClient 类的代码相对比较简单，主要就是根据远程服务的地址通过 Socket 发起通信，传入一个 Protocol 对象并返回远程调用的结果。&lt;/p&gt;
&lt;p&gt;完成了 RpcServer 和 RpcClient 类之后，我们就可以编写一些测试用例来进行验证。验证方法就是启动 RpcServer，然后通过 RpcClient 执行远程调用。这里我们编写一个 ServerTest 来启动 RpcServer，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82655117883759800000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ServerTest {
    public static void main(String[] args) {
        Map&lt;String, Object&gt; servicePool = new HashMap&lt;String, Object&gt;();
        servicePool.put(&amp;quot;com.juejin.rpc.service.UserService&amp;quot;, new UserServiceImpl());

        RpcServer server = new RpcServer(servicePool, 4, 9000);

        try{
            server.service();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}`, `82655117883759800000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; servicePool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        servicePool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.juejin.rpc.service.UserService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;servicePool&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后我们再编写一个 ClientTest 对远程服务发起请求，整个过程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37832533517174840000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ClientTest {
    public static void main(String[] args) {
        String serverAddress = &amp;quot;127.0.0.1&amp;quot;;
        int serverPort = 9000;

        RpcClient client = new RpcClient(serverAddress, serverPort);
        Protocol protocol = buildProtocol(&amp;quot;user1&amp;quot;);
        Object result = client.sendAndReceive(protocol);
        System.out.println(result);

        protocol = buildProtocol(&amp;quot;user2&amp;quot;);
        result = client.sendAndReceive(protocol);
        System.out.println(result);
    }

    private static Protocol buildProtocol(String userCode) {
        String interfaceName = &amp;quot;com.juejin.rpc.service.UserService&amp;quot;;
        Class[] paramsTypes = {String.class};
        Object[] parameters = {userCode};
        String methodName = &amp;quot;getUserNameByCode&amp;quot;;

        Protocol protocol = new Protocol(interfaceName, methodName, paramsTypes, parameters);
        return protocol;
    }
}`, `37832533517174840000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serverAddress &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; serverPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serverAddress&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; serverPort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendAndReceive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        protocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendAndReceive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; interfaceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.juejin.rpc.service.UserService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; paramsTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; parameters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;userCode&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getUserNameByCode&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paramsTypes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;至此，我们构建了一个简洁但又完整的 RPC 架构。通过这个示例，我们可以对 RPC 的整体结构有一个清晰的认识。事实上，无论是多么复杂的 RPC 框架，都是从这样的基础架构开始逐步演进而来。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-4&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;围绕 RPC 架构，我们首先要明确的第一个解题要点就是它的组成结构。RPC 架构的组成呈现一种非常标准的对称结构，围绕远程调用过程，我们可以提炼出一组分别针对服务消费者和服务提供者的技术组件。把各个技术组件进行分类梳理是一种有助于记忆的学习方法，你也可以尝试能不能对这一组成结构再做进一步的细化。&lt;/p&gt;
&lt;p&gt;RPC 架构的组成结构偏向理论描述，想要理解该架构中各个技术组件的具体实现过程，一种有效的方法就是自己动手做一些实践。而面试官实际上也非常看重候选人的这种实践能力，所以会以发散式的提问方式来考查候选人对 RPC 架构的掌握能力。基于本讲中给出的案例，你可以结合 RPC 架构的理论知识以及具体的代码实现过程来应对这种发散式的面试题。&lt;/p&gt;
&lt;p&gt;最后，从本讲开头罗列的一些面试题可以看出，面试官往往会把 RPC 架构的组成结构和具体的 RPC 开源框架组合在一起进行提问。而在本讲内容中，我们也发现组成 RPC 架构的这些技术组件具有演进性。我们可以从最基本的组件出发，逐步进行扩展和完善，直至形成一个完整的技术体系。也就是说，我们可以从一个基础架构出发来系统掌握大型开源框架的实现原理。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-2&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;想要掌握分布式服务框架，首先得掌握 RPC 架构。&lt;/p&gt;
&lt;p&gt;本讲内容对 RPC 架构的组成结构进行了详细的介绍。同时，我们也针对 RPC 架构所需要实现的各个技术组件，提供了一个简洁但又完整的示例，从而有助于你对该架构有一个感性的认识。可以说，RPC 是分布式系统中一项基础设施类的技术体系，但凡涉及到服务与服务之间的交互就需要使用到 RPC 架构。当你在使用一个分布式框架时，可以尝试使用今天介绍的 RPC 架构的基本结构进行分析，从而加深对这项技术体系的理解。&lt;/p&gt;
&lt;p&gt;在掌握了 RPC 架构的基本结构并动手实现了一个简易版的示例之后，让我们回到主流的开源框架，看看这些框架是如何实现远程调用的。远程调用涉及到服务发布和服务引用这两个方面。我们将先对前者展开讨论，并回答这样一个问题：如何合理设计服务发布机制？我们下一讲再聊。&lt;/p&gt;
&lt;h1 id=&quot;远程调用：如何合理设计服务发布机制？&quot;&gt;&lt;a href=&quot;#%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%EF%BC%9A%E5%A6%82%E4%BD%95%E5%90%88%E7%90%86%E8%AE%BE%E8%AE%A1%E6%9C%8D%E5%8A%A1%E5%8F%91%E5%B8%83%E6%9C%BA%E5%88%B6%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;远程调用：如何合理设计服务发布机制？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们通过一个简单但又完整的示例演示了如何实现一套自定义的 RPC 架构。在现实开发场景中，我们当然不建议大家重复造轮子，而是应该采用主流的 Dubbo、Spring Cloud 等开源框架来构建分布式系统。对于这些开源框架而言，RPC 架构的实现过程显然不会像上一讲中的示例那样简单，而是内置了各种强大的功能，从而为开发人员提供更好的开发体验。&lt;/p&gt;
&lt;p&gt;本质上，一次远程调用涉及两个角色，即服务提供者和服务消费者，分别用来实现服务的发布和引用。在本讲内容中，我们将讨论服务的发布过程，并引出一个常见的面试题，即：如何合理设计服务发布机制？&lt;/p&gt;
&lt;h2 id=&quot;问题背景-2&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，系统的能力来自于服务与服务之间的交互和集成。为了实现这一过程，就需要服务提供者对外暴露可以访问的入口。假设我们沿用上一讲中所介绍的业务服务 UserService，那么对该服务进行发布的一种实现方式如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40971885549605000000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`UserService service = new UserServiceImpl(...)；
RpcServer server = new RpcServer(…);
server.export(UserService.class, options);`, `40971885549605000000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserServiceImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;；
&lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码看上去也很简单，和我们在上一讲中所实现的示例代码有点类似。但在这几行代码的背后，我们需要考虑的事情非常多，比方说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果远程调用发生在同一个 JVM 中，服务发布应该如何实现？&lt;/li&gt;
&lt;li&gt;如果我们想要在服务发布过程中添加一些定制化的切面逻辑，应该怎么做？&lt;/li&gt;
&lt;li&gt;如果服务提供者具有多个服务实例，那么服务发布过程如何实现集群化？&lt;/li&gt;
&lt;li&gt;如果服务发布操作还没完成，服务消费者的请求就进来了应该怎么办？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题面向服务发布主流程中的特定场景，但正是这些特定场景才是面试过程中经常会出现的提问方式。同时，我们也应该注意到，远程调用是一个消耗大量资源的过程，资源的利用率也是框架必须要考虑的问题。如何合理地利用资源，避免远程通信所导致的性能消耗，也是服务发布机制设计上的一个重点。&lt;/p&gt;
&lt;p&gt;如果针对 Dubbo 等具体的实现框架，面试官也可以这样来进行提问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dubbo 中所采用的服务发布流程是怎么样的？&lt;/li&gt;
&lt;li&gt;Dubbo 框架中提供了哪几种服务发布机制？&lt;/li&gt;
&lt;li&gt;为了提高资源利用的效率，Dubbo 在发布过程中做了哪些优化？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题看上去侧重点各有不同，但其实都是围绕着一套服务发布流程来展开的，让我们来对这些问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-3&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;和前面几讲介绍的网络通信、序列化等主题不同，服务的发布和引用更多关注的是应用层的设计方法，而不是面向底层网络的通信组件。因此，我们对服务发布的分析也需要从应用层的角度进行切入，梳理一套通用而又完整的服务发布流程。而为了实现这一目标，就需要对业界主流的一些开源框架做一定的提炼和抽象。这是我们在应对这类面试题要把握的第一个方向。&lt;/p&gt;
&lt;p&gt;然后，我们在这套流程中进一步提炼服务发布过程中所使用到的各个技术组件，这些技术组件能够与前面给出的特定场景下的面试问题相对应。这是我们要回答的第二个主要方向。&lt;/p&gt;
&lt;p&gt;最后，我们还是要回到具体的实现框架，从源码切入来详细分析开源框架中针对服务发布机制的实现原理。只有这样，才能做到理论联系实际，也有助于我们更好地掌握开源框架的使用方式和技巧。&lt;/p&gt;
&lt;p&gt;上述三个维度构成了我们回答这类问题的解答思路。接下来要讨论的就是具体的技术体系了，让我们给出对服务发布流程的统一描述。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-5&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在现代的分布式架构中，服务的发布和引用过程往往与注册中心密切相关。服务注册中心是服务发布和引用的媒介，当我们把服务信息注册到注册中心，并能够从注册中心中获取服务调用地址时，需要考虑的问题就是如何进行有效的服务发布和引用。我们会在后面介绍注册中心，而在本讲接下来的内容中，我们重点关注的是服务发布所具备的流程和特性。&lt;/p&gt;
&lt;p&gt;我们知道服务发布的目的是将该服务的访问入口暴露给分布式系统中的其他服务。抛开具体的技术和框架，我们先可以简单抽象出如下图所示的服务发布的整体流程：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-5fd40.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 44.34389140271493%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABJUlEQVQoz21R2W7CQAzM//9RK14Q7R+gQgK0DyGBHJD7PqYZS45WtJYcx+uZ8dprOfYWtr3B9brHNANNU+F82sE+bhCFP7jdHBwP7zg5HxiGDvM8L/VPHL7e4Hl70LIsxuW8xfdlB2uaJtyDO6IoQlmW6PteQDxXG8dRnGJ01oZhWHO6YiwmnuchDMOlUyb+fD6XmzYCYAMlm8aaaSpq8VPXtRAp0nUd8jxHHMereBAEqKoKaZqKJ0kiOUXatpWG5DKKIJPXbgSqsSFzdYq5rivCXBPPGFdBk6yCvKlaURSyY8bH47FGCvwZmQtmJxb16joy10AQBfSWWZrJCnzfF55yiSdXBJX8nyDPKGa+Ov91An0sNl5HNsdTkDkO/01BHe8VT/8FHIa6Ijvw2goAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 23 11 11 30&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-fee1c.png&quot; data-srcset=&quot;/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-a67b7.png 200w,
/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-0b187.png 400w,
/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-fee1c.png 800w,
/static/2024-10-23-11-11-30-d9399ba4e9a0faa71418135cb54500cd-5fd40.png 884w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图包含了服务发布过程中的核心组件，我们来对这些核心组件做一一展开。&lt;/p&gt;
&lt;ol&gt;
&lt;li&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;使用注解：使用 Java 中的注解机制；&lt;/li&gt;
&lt;li&gt;API 调用：使用底层的代码接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上三种方式各有利弊。在日常开发过程中，配置和注解比较常用，而 API 调用则主要完成服务与服务之间的集成。&lt;/p&gt;
&lt;p&gt;讲完发布形式，我们来讨论如何启动服务发布过程，常见的策略如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-23-11-12-57-ea671c1b0957c2c513623988e7872a8b-b1313.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 488px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.72131147540984%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB0ElEQVQoz3VSDW+aUBTl//+IJV221S1NNUu6GZdmapFC1SpsCENFcCqypGJpwFmBs+MrW5olfcnLfR/n3nvuuVdCuYqiKG0u7GxmQderGNyew9Br0IdVYXvdDwhWnsDkeS78nm/pGOD5zvNMgH3fhqa+gdw+QfemwvM7dLVTKJ0ThKFfBszwv7+EF9Zms8E6CHF4zHB/H/Me4XDIsFoFiKLoJTdIvj+F61qYz8dY/JxgONCgaR2k6Q7LpU+mY/T7Cvo9RWCOb2maQlVlGEYPi4UrMK5r8+xDGpk17BIZ1ugME6eG/e8O3GkdQbDGlBboYe5fYDKusUYNq2ULjuOy7DZ+hQ3Y1jmcH1VEm0sSakGyLZXga4p/SZ0+Y2Q2me0WSZLAm+kwvzfxzWjTUWEiFRbxnucxiEZsi/rW+d/kv4yZO4TERmG/z5A/NZdlhdhuU3EOw4hMN4jjhLrFvG9pE/G32+WchCUeD09+xxgZ+8mmFGXHCpZmYjisk+lHMriCfHVGTRswTQMPDzFsW6ZemmBq6A0oSo13lRUZf4fvODZFmfFA2k0M+m/RuzmFqryGPqhQ2yru7tYi4Ni5wNz7hHbrFRO9J7ZCHb8w4dd/M/wHQcWSVJRbdk4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 23 11 12 57&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-23-11-12-57-ea671c1b0957c2c513623988e7872a8b-b1313.png&quot; data-srcset=&quot;/static/2024-10-23-11-12-57-ea671c1b0957c2c513623988e7872a8b-152e1.png 200w,
/static/2024-10-23-11-12-57-ea671c1b0957c2c513623988e7872a8b-4a935.png 400w,
/static/2024-10-23-11-12-57-ea671c1b0957c2c513623988e7872a8b-b1313.png 488w&quot; data-sizes=&quot;(max-width: 488px) 100vw, 488px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，我们可以使用 Spring 容器来完成基于配置化和注解形式下的服务启动过程。而对于 API 调用而言，由于不一定会借助于容器，所以也可以直接使用 main 函数来实现这一目标。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态代理&lt;/p&gt;
&lt;p&gt;动态代理是远程过程调用中非常核心的一个技术组件，在服务发布和服务引用过程中都会用到，其主要作用就是简化服务发布和引用的开发难度，以及确保能够对发布过程进行扩展和定制。&lt;/p&gt;
&lt;p&gt;关于动态代理的具体实现方式值得专门进行讨论，我们将在后面重点关注这一话题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发布管理器&lt;/p&gt;
&lt;p&gt;服务发布过程需要使用专门的组件来进行统一管理，这个组件就是发布管理器。该组件需要判断本次发布是否成功，然后在服务发布成功之后，把服务的地址信息注册到注册中心。而这里的服务地址信息则来自于协议服务器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;协议服务器&lt;/p&gt;
&lt;p&gt;在服务发布过程中，在物理上真正建立网络连接，并对网络端口进行绑定的组件是协议服务器。协议服务器还会执行心跳检测以及在连接失败之后进行重连操作。用于发布服务的常见协议包括 HTTP、RMI、Hessian 等。我们也可以自己定义这样的协议，例如 Dubbo 框架就实现了一套自定义的 Dubbo 协议。实际上，关于网络通信方面的讨论就是针对这里的协议服务器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注册中心&lt;/p&gt;
&lt;p&gt;注册中心的作用是存储和管理服务定义的各类元数据，并能感知到这些元数据的变化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上所示的服务发布流程图有一定的共性，可以通过转化映射到具体的某个框架。事实上，基于 Dubbo 的服务发布流程与上述过程非常类似。让我们来一起看一下。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-3&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在具体介绍 Dubbo 服务发布流程之前，我们先来讨论 Dubbo 暴露服务的两种时效，一种是延迟暴露，一种是正常暴露，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-23-11-14-30-28a2a1fb63f26088d4209a9dd78a7b17-ced8c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 491px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.47250509164969%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACFElEQVQoz12S627aQBCFef8H6M+qatWGS5OUaxKSiLahEkmJbWwuhoDBgCGAgdiGBAr49HhpaZWVVjs7O3M08+2EfN+H7+8QrE5HhyydolLOQC0locgJKEoCJSWw4+JerWR4JjEaD0XO7k+u0OEZCozdbu80DBVGi4lSFHeFj7gtfICqRCkYof0eZfUzjGYSZucMvV57L7jb7sV8X9xD+G+tVi8wzRZGIwu6XoaqSSKx2zWgaTJUVcJgYGI47B+6+rsOgoExteeYTudwHA+LxQuTq6yij9drybdKRYdlDeG6C8znjsh7enIPoiHDqMHqf0XXzKH5kCWjFFs8oX3GausicLvdcAPNZhGd9jn0WoZoLlGrptHv5dBqXmMyHu8r1HWFjiTui0fI37zDxfkbFH8eoaxF6NcOnALMmvoNbSMB6T6M/Pe3yDJWkcMsIo7hwPrH0HVdbDa/0GjU+TEtYQe+13y2LNN1HazXK8iyBNueYL1aEdPiEBuazWx4novn5yVhDzCZjLBcLhjkko9Nro4InM2mYnuewzePn9Ulu5nIdZw588ZCI2T1LzgOxyjJUbYdRkU7RvHuEyTaZidNTpdk10KjnsLj4IrtnXCUYpzXCGc1xvcv5H+O0eMV8vlkUGFBDGuJA3xbiJFdijtN4QzsyQ9+QoGfYxL+DVyniIdGliwD5qcUjaPduuaUFOC5Rd5z+A3TadbnwaLV0QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 23 11 14 30&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-23-11-14-30-28a2a1fb63f26088d4209a9dd78a7b17-ced8c.png&quot; data-srcset=&quot;/static/2024-10-23-11-14-30-28a2a1fb63f26088d4209a9dd78a7b17-680fb.png 200w,
/static/2024-10-23-11-14-30-28a2a1fb63f26088d4209a9dd78a7b17-eda79.png 400w,
/static/2024-10-23-11-14-30-28a2a1fb63f26088d4209a9dd78a7b17-ced8c.png 491w&quot; data-sizes=&quot;(max-width: 491px) 100vw, 491px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，如果这里的 delay 参数被设置成了 -1，代表不需要延迟暴露。反之，Dubbo 会根据该参数值的大小来执行对应的延迟策略。讲到这里，你可能会问，Dubbo 为什么要考虑发布时效这个问题呢？主要目的在于提供平滑发布机制。如果 Dubbo 服务本身还没有完全启动成功，那这时候对外暴露服务是没有意义的，我们可以通过设置延迟时间来确保服务在发布的时间点上就是可用的。&lt;/p&gt;
&lt;p&gt;在判断是否需要延迟暴露之后，ServiceBean 就会调用 export 方法执行服务暴露。而 export 方法又来自 ServiceBean 的父类 ServiceConfig，所以关于 Dubbo 服务发布的流程就从 ServiceConfig 类进行切入。&lt;/p&gt;
&lt;p&gt;那么，ServiceConfig 是如何实现延迟暴露的呢？实际上很简单，就是启动一个后台线程来延迟调用一个 doExport 方法，该方法负责执行具体的服务暴露逻辑。而如果没有采用延迟暴露策略，那么这个 doExport 方法就会被立即执行，具体的执行流程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50801116285753300000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 将 Bean 属性转化为 URL
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? &amp;quot;&amp;quot; : contextPath + &amp;quot;/&amp;quot;) + path, map);

String scope = url.getParameter(Constants.SCOPE_KEY);
// 如果 scope 配置为 none 则不暴露
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
            // 如果 scope 没有被配置为远程暴露，则采用本地暴露
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // 如果 scope 没有被配置为本地暴露，则采用远程暴露
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (registryURLs != null &amp;&amp; registryURLs.size() &gt; 0) {
                    for (URL registryURL : registryURLs) {
                        // 将具体服务转化为 Invoker
                        Invoker&lt;?&gt; invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        // 将 Invoker转化为 Exporter
                        Exporter&lt;?&gt; exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker&lt;?&gt; invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter&lt;?&gt; exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            }
}
this.urls.add(url);`, `50801116285753300000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 将 Bean 属性转化为 URL&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; host&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contextPath &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; contextPath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; contextPath &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCOPE_KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 如果 scope 配置为 none 则不暴露&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCOPE_NONE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果 scope 没有被配置为远程暴露，则采用本地暴露&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCOPE_REMOTE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;exportLocal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果 scope 没有被配置为本地暴露，则采用远程暴露&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCOPE_LOCAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registryURLs &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; registryURLs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;URL registryURL &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; registryURLs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 将具体服务转化为 Invoker&lt;/span&gt;
                        &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; registryURL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addParameterAndEncoded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXPORT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFullString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token class-name&quot;&gt;DelegateProviderMetaDataInvoker&lt;/span&gt; wrapperInvoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DelegateProviderMetaDataInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                        &lt;span class=&quot;token comment&quot;&gt;// 将 Invoker转化为 Exporter&lt;/span&gt;
                        &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wrapperInvoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        exporters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;DelegateProviderMetaDataInvoker&lt;/span&gt; wrapperInvoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DelegateProviderMetaDataInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                    &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wrapperInvoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    exporters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段代码值得仔细分析。在这里，我们可以看到首先会构建一个 URL 对象，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;66269595841064310000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? &amp;quot;&amp;quot; : contextPath + &amp;quot;/&amp;quot;) + path, map);`, `66269595841064310000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; host&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contextPath &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; contextPath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; contextPath &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，在 Dubbo 中，URL 对象代表了统一数据模型，会贯穿服务暴露和调用的整个流程，绝对是一等公民。URL 格式如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;80327275212678400000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protocol://username:password@host:port/path?key=value&amp;key=value`, `80327275212678400000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;protocol://username:password@host:port/path?key=value&amp;amp;key=value&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当不使用注册中心时，URL 表现形式比较简单，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;64723427207965380000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`dubbo://service-host/com.foo.FooService?version=1.0.0`, `64723427207965380000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dubbo://service-host/com.foo.FooService?version=1.0.0&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而当使用注册中心时，URL 中会带有注册中心信息，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35279149253045980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode(&amp;quot;dubbo://service-host/com.foo.FooService?version=1.0.0&amp;quot;)`, `35279149253045980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode(&amp;quot;dubbo://service-host/com.foo.FooService?version=1.0.0&amp;quot;)&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过分析 Dubbo 源码，我们得到将配置信息通过一种统一的 URL 进行表示和传递的实现方法，这也是值得我们学习的一个设计技巧。&lt;/p&gt;
&lt;p&gt;接着，我们根据 scope 参数判断服务的发布范围，判断规则如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-23-11-16-33-0c6cef9736f26464b42ab0bb0f58c507-c6c9e.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 606px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.303630363036305%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABeUlEQVQoz12RW0/CQBCF+/9/idEoETAqohgoKveLUtoutGy3tKU8l4Rbj6frC7HJNDu7s2e+OWvkeY48P+N8PgPIcTgcsRBNDPvX6HauMRrewvfqEELAshpQwQsC+Qp7XkXn6wrDwQ0U80DWdRj49xXCK38O1xlgvXYRrR0kyRJZlmG7DZHELjabJfcEzx3EkUCq84UOI01DRJGPOF6xWLI4pZiJufWIybiK7+kDQmXC85ak/MQmaWMdmrBmT+j3Suh17wjwzrsf1GnDkKsaBe6wcEsIgxq7hpDS0SRhaDO3dec/QsXGjqaLIkefFxGztthLYkFBOYBwWxRt0qsOlFLaq+mkjPGoQsoyvGWDhB5ct81LLRI3MZ1WSHerw/ffOLpJe1owTqccl7Hf7znaN2y7z9EcPsJce7nLdtimipYIjr3QZIG0EASWJit8LEgvHiXX/+PxCLn6okcP2ifXeeZ4H6Tw2cQk+T1mP4/0tsqzGmv+1sUkiuS/afX3ELJl4DkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 23 11 16 33&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-23-11-16-33-0c6cef9736f26464b42ab0bb0f58c507-c6c9e.png&quot; data-srcset=&quot;/static/2024-10-23-11-16-33-0c6cef9736f26464b42ab0bb0f58c507-c7209.png 200w,
/static/2024-10-23-11-16-33-0c6cef9736f26464b42ab0bb0f58c507-2eadc.png 400w,
/static/2024-10-23-11-16-33-0c6cef9736f26464b42ab0bb0f58c507-c6c9e.png 606w&quot; data-sizes=&quot;(max-width: 606px) 100vw, 606px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;关于本地服务的暴露我们后面再具体展开，这里先讨论远程服务暴露的场景。&lt;/p&gt;
&lt;h3 id=&quot;远程服务暴露&quot;&gt;&lt;a href=&quot;#%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;远程服务暴露&lt;/h3&gt;
&lt;p&gt;在 Dubbo 中，远程服务暴露过程需要执行非常重要的一个步骤，即将具体服务对象转换到 Invoker，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27742427079517130000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Invoker&lt;?&gt; invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));`, `27742427079517130000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; registryURL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addParameterAndEncoded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXPORT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFullString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里看到了一个 proxyFactory 对象，从命名上不难看出它是一个代理工厂类，定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;42075055919491280000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;javassist&amp;quot;)
public interface ProxyFactory {
   @Adaptive({Constants.PROXY_KEY})
   &lt;T&gt; T getProxy(Invoker&lt;T&gt; invoker) throws RpcException;

   @Adaptive({Constants.PROXY_KEY})
   &lt;T&gt; Invoker&lt;T&gt; getInvoker(T proxy, Class&lt;T&gt; type, URL url) throws RpcException;
}`, `42075055919491280000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;javassist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProxyFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROXY_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROXY_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Dubbo 中，该接口有两个实现类，即 JdkProxyFactory 和 JavassistProxyFactory。我们来看 JdkProxyFactory 中的 getInvoker 方法实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43487625572706160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Invoker&lt;T&gt; getInvoker(T proxy, Class&lt;T&gt; type, URL url) {
        return new AbstractProxyInvoker&lt;T&gt;(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, Class&lt;?&gt;[] parameterTypes, Object[] arguments) throws Throwable {
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                return method.invoke(proxy, arguments);
            }
        };
}`, `43487625572706160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractProxyInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doInvoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; parameterTypes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameterTypes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这里返回了一个 AbstractProxyInvoker 对象，而 AbstractProxyInvoker 实现了 Invoker 接口。Invoker 接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11298621140666931000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Invoker&lt;T&gt; extends Node {
   // 获取服务接口
   Class&lt;T&gt; getInterface();
   // 执行远程调用
   Result invoke(Invocation invocation) throws RpcException;
}`, `11298621140666931000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取服务接口&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInterface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 执行远程调用&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dubbo 为我们提供了一组 Invoker 接口的实现类，其中包括前面看到的 AbstractProxyInvoker。&lt;/p&gt;
&lt;p&gt;我们已经在 JavaProxyFactory 中看到了构建一个 Invoker 对象的实现过程，背后使用的本质上就是反射机制。&lt;/p&gt;
&lt;p&gt;现在我们明白了，Invoker 就是 Dubbo 实现远程调用的实体类。对于服务发布和引用而言，这个 Invoker 贯穿始终，可以说也是一个一等公民。&lt;/p&gt;
&lt;p&gt;有了 Invoker 之后，我们再来关注 Exporter。我们看到 Exporter 是通过调用 Protocol 接口的 export 方法所生成的，这就需要我们对 Procotol 做一些回顾。Protocol 作为 Dubbo 中最基本的 RPC 组件，完成了服务的发布和调用功能，该接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11904556284105540000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;dubbo&amp;quot;)
public interface Protocol {
int getDefaultPort();
   @Adaptive
   &lt;T&gt; Exporter&lt;T&gt; export(Invoker&lt;T&gt; invoker) throws RpcException;

   @Adaptive
   &lt;T&gt; Invoker&lt;T&gt; refer(Class&lt;T&gt; type, URL url) throws RpcException;

   void destroy();
}`, `11904556284105540000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dubbo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDefaultPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从方法命名上不难看出，Protocol 接口中的核心方法就是 export 和 refer，前者用于对外暴露服务，而后者则用来对远程服务进行引用。当 Dubbo 获取 URL 之后会将 URL 传给 Protocol，Protocol 根据 URL 的协议头执行不同协议的服务暴露或引用。&lt;/p&gt;
&lt;p&gt;关于 URL 的格式我们在前面已经给出说明和示例。根据 URL 中是否包含注册中心信息，服务发布流程也会判断是否需要与注册中心进行交互。关于注册中心的讨论我们放在后面进行详细展开。这里给出不基于注册中心的服务暴露方式，即如下所示的 DubboProtocol 中 export 方法的实现过程。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45143026428320930000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Exporter&lt;T&gt; export(Invoker&lt;T&gt; invoker) throws RpcException {
   URL url = invoker.getUrl();

   // 暴露服务
   String key = serviceKey(url);
   DubboExporter&lt;T&gt; exporter = new DubboExporter&lt;T&gt;(invoker, key, exporterMap);
   exporterMap.put(key, exporter);

   // 创建 Exchange 服务器
   openServer(url);

   return exporter;
}`, `45143026428320930000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 暴露服务&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;serviceKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;DubboExporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DubboExporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exporterMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   exporterMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 创建 Exchange 服务器&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;openServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; exporter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 openServer 方法的目标是创建 Exchange 服务器，这是整个服务发布流程中的底层部分，我们已经在 &lt;code class=&quot;language-text&quot;&gt;网络通信：如何完成客户端和服务端之间的高效通信&lt;/code&gt; 中做了详细介绍，你可以做相应的回顾。&lt;/p&gt;
&lt;p&gt;作为总结，我们把整个远程服务暴露流程做一个梳理，得到如下图所示的核心对象交互图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-23-11-18-42-87bae7206359cc47845df0f71f52577d-53b00.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 608px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 58.7171052631579%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABqklEQVQoz5WTW0/CQBCF+f+/g8QnH4w+GBQ1EhHBC9gLUCi03NpSir1KgbbH2SUhxFCMTSbTbjpnv50zW2h+XOG1cYbqU5FH/aWITucW398rsCfLMvznKSydKWxbw3jchabJsKwBXNdAkiRMjQsei7zNCuNRmwQVSFIVsvxM713MZgq22+1JkjzygvBZwnBwg0b9HM2PCwzUEtpyGYvFEmEYEq1POdqH5wVI0zSfMI+A1fSUJibjMtReCb3uNX1fw5g9UFv004JBEGA+t4hqwbNtzxFFEeI4woha0u8LaDVrULot6LpMpP5e8HdwwTiO4fs+F2bZ81xaW8NxLEwnLRiGiPe3MqZTAZYp0rqZ7/KpsTCMIR25QqJkmFQiugpM44mOLmCzSWi01pRTMjDDep0QxHZHmDcaaZpR0Yb/zIpY8Wq1c3846EDtlzHSH8lEttkj0dfyTTkkd10Xy6VDPbZ5+H6AMPiCPZfJJBGSWMFk8gln0TkueDi8bERYXx3H4cIsB0FIff7iF0HT2nTTKlBVkUSVfMK/rp1panyERvodJOGSen0Pz63jBxgqlI5VU+0pAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 23 11 18 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-23-11-18-42-87bae7206359cc47845df0f71f52577d-53b00.png&quot; data-srcset=&quot;/static/2024-10-23-11-18-42-87bae7206359cc47845df0f71f52577d-17576.png 200w,
/static/2024-10-23-11-18-42-87bae7206359cc47845df0f71f52577d-52273.png 400w,
/static/2024-10-23-11-18-42-87bae7206359cc47845df0f71f52577d-53b00.png 608w&quot; data-sizes=&quot;(max-width: 608px) 100vw, 608px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;关于 Dubbo 中远程服务暴露的实现过程就介绍到这里，接下来我们讨论本地服务暴露过程。&lt;/p&gt;
&lt;h3 id=&quot;本地服务暴露&quot;&gt;&lt;a href=&quot;#%E6%9C%AC%E5%9C%B0%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;本地服务暴露&lt;/h3&gt;
&lt;p&gt;我们回顾 ServiceConfig 中的服务暴露流程，存在如下所示的一个流程分支：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;46478162731404526000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
   exportLocal(url);
}`, `46478162731404526000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCOPE_REMOTE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;exportLocal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，如果 scope 没有被设置为远程暴露，则采用本地暴露模式暴露服务。那么，什么样的场景适合这种服务暴露模式呢？我们知道远程调用只会发生在跨 JVM 的场景，如果在同一个 JVM 中同时存在某一个服务的提供者和消费者，那么就可以将服务发布和引用过程控制在同一个 JVM 之内，从而避免远程网络通信所导致的性能消耗。&lt;/p&gt;
&lt;p&gt;Dubbo 中提供的 exportLocal 方法实现了这一过程，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;77641346078987960000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void exportLocal(URL url) {
   if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
      URL local = URL.valueOf(url.toFullString())
               .setProtocol(Constants.LOCAL_PROTOCOL)
               .setHost(LOCALHOST)
               .setPort(0);
      Exporter&lt;?&gt; exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
      exporters.add(exporter);
   }
}`, `77641346078987960000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;exportLocal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCAL_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; local &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; URL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFullString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCAL_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LOCALHOST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; local&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      exporters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的特殊之处就在于当碰到场景为 local 的 URL 时，我们将它的协议设置为了 &lt;code class=&quot;language-text&quot;&gt;Constants.LOCAL_PROTOCOL&lt;/code&gt;，即 &lt;code class=&quot;language-text&quot;&gt;injvm&lt;/code&gt;。我们不难猜到 Protocol 接口应该存在一个名为 InjvmProtocol 的实现类。跟 DubboProtocol 相比，InjvmProtocol 的 export 方法就显得非常简单了，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54022058832412440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Exporter&lt;T&gt; export(Invoker&lt;T&gt; invoker) throws RpcException {
   return new InjvmExporter&lt;T&gt;(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}`, `54022058832412440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InjvmExporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getServiceKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exporterMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里没有执行任何关于远程调用的操作，而是构建了一个 InjvmExporter 对象并直接返回，InjvmExporter 类的定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;49723872789255160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class InjvmExporter&lt;T&gt; extends AbstractExporter&lt;T&gt; {
   private final String key;
   private final Map&lt;String, Exporter&lt;?&gt;&gt; exporterMap;

   InjvmExporter(Invoker&lt;T&gt; invoker, String key, Map&lt;String, Exporter&lt;?&gt;&gt; exporterMap) {
      super(invoker);
      this.key = key;
      this.exporterMap = exporterMap;
      exporterMap.put(key, this);
   }

   public void unexport() {
      super.unexport();
      exporterMap.remove(key);
   }

}`, `49723872789255160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InjvmExporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractExporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporterMap&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;InjvmExporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; exporterMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exporterMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; exporterMap&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      exporterMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unexport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unexport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      exporterMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看到这里，我们明白了所谓的本地暴露，Dubbo 只是将 InjvmExporter 对象放置到一个 Map 内存对象中。这样，我们就可以直接从 JVM 的内存中获取 InjvmExporter 对象来完成服务之间的调用。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-5&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;本讲介绍的服务发布机制以及下一讲要介绍的服务引用过程都是属于技术实现流程类的面试题，这种面试题想要回答得很到位是有难度的，需要具备高度的抽象能力。本讲对这些流程进行了梳理，面试者只要对有一个基本的认识，就能从动态代理、协议、注册中心等关键技术组件出发，并对这些组件的作用和运行机制做一些展开。这样，面试官的主要考察目标基本就能满足了。&lt;/p&gt;
&lt;p&gt;请注意，考查技术实现流程性的面试题还有一个特点，就是它会关联到多个技术组件，相信你从本讲中所引用的其他各讲的数量就能感受到这一点。服务的发布需要注册中心，也需要底层的网络通信。在面试过程中，我们需要点到这些关联性的技术组件，并挑选自己比较熟悉的方向进行展开。&lt;/p&gt;
&lt;p&gt;最后，我们同样建议基于具体的开源框架来展开对技术实现流程的讨论。对于 Dubbo 框架，建议不用做过多展开，只需要点到最核心的几个概念即可，例如远程服务暴露和本地服务暴露这两种服务暴露类型。然后，我们也可以重点对 Dubbo 框架中的核心对象做一些分析，例如 URL 对象和 Invoker 对象。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-3&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;在远程过程调用的实现流程上，主要包括服务发布和服务引用两大维度。&lt;/p&gt;
&lt;p&gt;本讲内容围绕远程调用的发布流程展开了详细的讨论，这部分内容是我们构建分布式系统的基本前提。同时，基于这套服务发布流程，我们对 Dubbo 这款主流的分布式服务框架的内部实现原理，即如何完成远程/本地服务暴露的过程进行分析。&lt;/p&gt;
&lt;p&gt;介绍完服务发布流程和实现原理之后，下一讲我们继续讨论远程过程调用，我们要回答的一个核心问题是：服务引用有哪些实现方式？同样，我们也会基于 Dubbo 框架给出这个问题的答案。&lt;/p&gt;
&lt;h1 id=&quot;远程调用：服务引用有哪些实现方式？&quot;&gt;&lt;a href=&quot;#%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8%E6%9C%89%E5%93%AA%E4%BA%9B%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;远程调用：服务引用有哪些实现方式？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们介绍了远程过程调用中的服务发布流程。我们知道服务发布的过程就是服务提供者对外暴露可访问入口的过程。基于所暴露的访问入口，服务消费者就可以成功发起远程调用。我们把这个过程称为服务引用。&lt;/p&gt;
&lt;p&gt;和服务发布类似，服务引用也具备一套完整的执行流程。那么，服务引用有哪些具体的实现方式呢？这就是本讲要介绍的内容。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-3&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，系统的能力来自于服务与服务之间的交互和集成。为了实现这一过程，就需要服务消费者对服务提供者所暴露的入口进行访问。假设我们继续使用 UserService 作为业务接口，那么服务引用的一种表现形式可以采用如下所示的代码风格：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;523280480351706600&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`RpcClient client = new RpcClient(…);
UserService service = client.refer(UserService.class);
service.getUserNameByCode(&amp;quot;user1&amp;quot;);`, `523280480351706600`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserNameByCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&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;li&gt;如何实现异步调用、泛化调用等多种调用形式？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际上，相较服务发布，服务引用是一个更为复杂的话题，涉及的技术组件众多。后续几讲要介绍的负载均衡、服务容错等机制都发生在服务引用阶段，这些机制对于服务发布而言是不需要考虑的。&lt;/p&gt;
&lt;p&gt;当然，如果针对 Dubbo 等具体的实现框架，面试官也可以这样来进行提问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dubbo 中所采用的服务引用流程是怎么样的？&lt;/li&gt;
&lt;li&gt;Dubbo 框架中提供了哪几种服务调用方式？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样，这些问题看上去侧重点各有不同，但其实都是围绕着一套服务引用流程来展开的，让我们来对这些问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-4&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;对于远程过程调用而言，服务引用和服务发布是两个对应的技术组件。因此，上一讲中所有关于服务发布的问题分析方法同样适用于服务引用过程，这部分内容这里就不一一展开，你可以参考上一讲内容做一些回顾。&lt;/p&gt;
&lt;p&gt;然而，对于服务引用而言，也存在与服务发布不一样的地方。首要一点在于服务引用的类型可以是多样的，我们可以使用同步调用、异步调用等多种方式来完成远程调用过程。&lt;/p&gt;
&lt;p&gt;在日常开发过程中，开发人员倾向于使用同步调用模式来完成远程调用，因为这一模式对于编码过程而言非常友好。而从性能上讲，异步调用模式显然更具优势，但实现复杂度较高。这就诞生了一种新的实现机制，即 &lt;code class=&quot;language-text&quot;&gt;异步转同步&lt;/code&gt;，诸如 Dubbo 等框架就内置了这种实现机制。&lt;/p&gt;
&lt;p&gt;在面试过程中，除了介绍通用的服务引用流程，候选人最好能够对服务的引用类型，以及异步转同步这一特定技术实现机制做一定的展开，这些都是提高面试成功率的加分项。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-6&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;h3 id=&quot;通用服务引用流程&quot;&gt;&lt;a href=&quot;#%E9%80%9A%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8%E6%B5%81%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;通用服务引用流程&lt;/h3&gt;
&lt;p&gt;相较服务发布，服务的调用是一个导入（Import）的过程，整体流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-7f86d.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.013245033112575%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABP0lEQVQoz41S206DQBTk/7/HS6NvPmuiVjSaIE0JCVDKpQXKfdw5eBTbaJxkwrI9nZ2ZxXpaXuLduUXXdRgGII59vNhXsJcLRJEH33/Fw/2Z4QJFkcvMdhvg2b7G8vEcnvcG170z6ws4zg2sqqqQ5xn2+z12ux3KskTf90IF1wOVZuA7TUwYzUyHcRxhhWFoXMUyQHJTRj6fulZB3Z8Lco9r/mRlWSaC5GazQZIkJoYnTg+HA5igKIof4qcOpxTikH/SiFOPg4gwfp7n8gyCwPS2lVqiKDK9+litVqY7V4xwjmYIETxG27ZCBQ+gIA/Vw5gsTVPUdS2zFP1yeNxb0zRCBSPTFUVYBd/J+cVR9E9BnqxgVEbSSyMppim+L+UXQQ6yG/bFqPolkHTJi1uv1+L234LzDvWTmoMOtZa54Af/NAdjn4yNWgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 24 26&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-fee1c.png&quot; data-srcset=&quot;/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-a67b7.png 200w,
/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-0b187.png 400w,
/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-fee1c.png 800w,
/static/2024-10-28-18-24-26-6262a7239637e37f87518d2ced6aa6b5-7f86d.png 906w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们可以看到服务调用流程与服务发布流程呈对称结构，所包含的组件包括以下。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;调用启动器。调用启动器和上一讲介绍的发布启动器是对应的，这里不再重复介绍。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态代理。在服务引用过程中，动态代理的作用就是确保远程调用过程的透明化，即开发人员可以使用本地对象来完成对远程对象的处理，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-26-12-948b7a967f9bbdf96f4c41e95f1683af-72de5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 648px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.462962962962965%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA8klEQVQY032Q3U7CQBCF9/1fRaQgqV6oaSRR0mKhW6TQFr3CG0KFYuv+0R6njYkXIif5srOzm8mZw3y/j3hpgwc9TP0uFtEVksSDMTXq+i9VVaFRHLtYLgYIphY4EXILaeqAcW7Tgw3PvcB43MHLzKKBLsyxxjmF/IFMdOA/dzEhI8HkEm+vQzAhFYQQUEq1lOUXis+i7W02Gd0l1RpCahSFIEporbDdZtjt9lRrSClbhJBg/zk4HHKMnvpIYgfz+T1m4S3SxEEUefjZ+qTYqZwa5fkeq9U13tc3tMqAhvXwkd1Rdo+Ub/PjN1NDDWN0e34DuVx1rTEJGREAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 26 12&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-26-12-948b7a967f9bbdf96f4c41e95f1683af-72de5.png&quot; data-srcset=&quot;/static/2024-10-28-18-26-12-948b7a967f9bbdf96f4c41e95f1683af-bb436.png 200w,
/static/2024-10-28-18-26-12-948b7a967f9bbdf96f4c41e95f1683af-c5634.png 400w,
/static/2024-10-28-18-26-12-948b7a967f9bbdf96f4c41e95f1683af-72de5.png 648w&quot; data-sizes=&quot;(max-width: 648px) 100vw, 648px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;/ol&gt;
&lt;p&gt;以上所示的服务引用流程图同样有一定的共性，可以通过转化映射到具体的某个框架。事实上，基于 Dubbo 的服务引用流程与上述过程也比较类似。&lt;/p&gt;
&lt;p&gt;另一方面，与服务发布相比，Dubbo 等分布式服务框架中的服务引用整体过程会更加复杂一点。在服务调用过程中，因为面对的服务一般都会部署成集群模式，势必涉及到负载均衡。而如果调用超时或失败，还会采用集群容错机制。关于负载均衡和集群容错等内容不在这里讨论，我们在介绍完本讲内容之后马上就会有专门的主题进行详细阐述。&lt;/p&gt;
&lt;h3 id=&quot;服务调用的类型&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E7%9A%84%E7%B1%BB%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务调用的类型&lt;/h3&gt;
&lt;p&gt;服务调用存在两种基本方式，即同步调用模式和异步调用模式。其中，同步调用的示意图如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-27-34-3094819fb5c977b04140eb1a5fc680b9-ff6e4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 479px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.39039665970772%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABPUlEQVQoz5WSW0vDQBCF8/9/TwUV8SWgfeqTtiXmatJkY5r7rbn1uDPY0FoqeGDYZWbny5khyvvbHcpiCcu8h2M94NN5Ql0uoWnPaNsOpOPx+BMTpgn40FRkqQpbvuc++xFF/opQvEAJggC+7yMMBWzbRhR9gXJxHOOW0jTFbrdDUeTw5el5LjNEIKD8ftg0DQ6Hg3TXIssyhuu6js1mg+12izzPuTYMIxzH4dq5lIlmkCKQ67ooy1IC2xmYJIl0HzKYQgjBUFrBer2GqqrzWoil0OUE3O/3WCwWcicOuq7jZhrFsiwYhsEnuSRwXdfcQ7lhGGboDKQxPc/jr/Z9z0B6SDGO4xzkgnJ913OPaZqcvwLSiASsqgr/kaZpbOAKSPajKGJnl7/K7SDRWs57LhyuVquL4l861cnd+cjfI06yip0BrwcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 27 34&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-27-34-3094819fb5c977b04140eb1a5fc680b9-ff6e4.png&quot; data-srcset=&quot;/static/2024-10-28-18-27-34-3094819fb5c977b04140eb1a5fc680b9-d5807.png 200w,
/static/2024-10-28-18-27-34-3094819fb5c977b04140eb1a5fc680b9-dba8d.png 400w,
/static/2024-10-28-18-27-34-3094819fb5c977b04140eb1a5fc680b9-ff6e4.png 479w&quot; data-sizes=&quot;(max-width: 479px) 100vw, 479px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，同步调用的执行流程比较简单。在同步调用中，服务消费者在获取来自服务提供者的响应结果之前一直处于等待状态。&lt;/p&gt;
&lt;p&gt;而异步调用则不同，服务消费者一旦发送完请求之后就可以继续执行其他操作，直到服务提供者异步返回结果并通知服务消费者进行接收，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-28-09-875b24a3310338fd19e7235601b15c93-96dff.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 494px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 105.46558704453442%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsSAAALEgHS3X78AAACWUlEQVQ4y51UiWrbQBD1//9IKYSGNIbQFkoxNgTqJvUhy64t+ZTk2/Ip36/71hlVShyDO7BotTvz5s2xk7AtA2UziXzuDr+fb5H9eYNK+QHFQhKe54ByOBxwPB7V7qj2QMl4hG19wdOvT3h+utVfq/YV6fQHJGzbQKf9TSk8KKB7mKXPcJ3vsJSB67Y14AnsJPs9UC4/YtD/gYpJ/Tv8KSfVf0qR+YjEZrPBZDLBfD7HeDzGaDTCYjHHdDoNme12O722260GXa/X8P0JgmClbEbanvr8JhARHvZ6fSyXK+1gNpuh2+2iVqvpZRiGuu8phwuls9TOLctSjv9FkCADMqG4rovBYBDmjayCINCOhsPhC/uFYn1ivlqtUK1WQ+Y8iwE2m03k8/mQHVPgeR7a7bZmYts2Op1OeO/7Pur1unYcAkZDdhxHs4hX9q3wXHLbarXiDOlVgPr9vs6ZJJgMeE6mXDzjP+9PFd9r9jHAXC6HQqGgc0dgglIYGs9fh0QQggvgG4Y0YMIJWCwWkUqldB7JgqASnghTQebvAjIMESpms1ldOSqz36J5E0CxOQsoRZAwqfAaIMryKkA2rYRzrrJXA/KCzUrhOfPLKkqfXg3IKrOBWWnuS6WSZvzfgNyn02lkMpmXAeDHGllALgJKT0kOOQQuCY0utg1fBhuXl8wZB4S0DEebjC7RobFEdRaQDS3PzFNgBGRT858Nz1yyMPzSmAOEE0gA2GoxwGg4DL9SqehZx0VAGjQajXDx7XPSyLQxTVNHcnZ80VjeMkN8b+IwVAmfTqIM/wLLqUdt7xdHBAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 28 09&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-28-09-875b24a3310338fd19e7235601b15c93-96dff.png&quot; data-srcset=&quot;/static/2024-10-28-18-28-09-875b24a3310338fd19e7235601b15c93-1a6e9.png 200w,
/static/2024-10-28-18-28-09-875b24a3310338fd19e7235601b15c93-b9533.png 400w,
/static/2024-10-28-18-28-09-875b24a3310338fd19e7235601b15c93-96dff.png 494w&quot; data-sizes=&quot;(max-width: 494px) 100vw, 494px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;显然，使用异步调用的目的在于获取高性能。但是，异步调用的开发过程比较复杂，对开发人员的要求较高，所以很多 RPC 框架提供了专门的异步转同步机制，即面向开发人员提供的是同步调用的 API，而具体执行过程则使用的是异步机制。&lt;/p&gt;
&lt;p&gt;除了同步和异步调用之外，还存在并行（Parallel）调用和泛化（Generic）调用等调用方法，这些调用方式并不常见，我们不做具体展开。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-4&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;与 ServiceConfig 中的 export 方法相对应，ReferenceConfig 中也存在一个 init 方法，该方法就是 Dubbo 服务引用流程的入口。&lt;/p&gt;
&lt;h3 id=&quot;服务引用&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务引用&lt;/h3&gt;
&lt;p&gt;在 ReferenceConfig 的 init 方法中，Dubbo 做了非常多的准备和校验工作，最终来到了如下所示的这行代码中：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;44593680979241700000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`ref = createProxy(map);`, `44593680979241700000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;ref &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个 createProxy 方法是理解服务引用的关键入口，我们梳理它的主体结构如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;89648564967083440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private T createProxy(Map&lt;String, String&gt; map) {
       if (isJvmRefer) {
            // 生成本地引用 URL，使用 injvm 协议进行本地调用
            URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
            invoker = refprotocol.refer(interfaceClass, url);
        } else {
            if (url != null &amp;&amp; url.length() &gt; 0) {
                // URL 不为空，执行点对点调用
            } else {
                // 加载注册中心 URL
            }

            if (urls.size() == 1) {
                // 单个服务提供者，直接调用
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
                // 多个服务提供者
                List&lt;Invoker&lt;?&gt;&gt; invokers = new ArrayList&lt;Invoker&lt;?&gt;&gt;();
                URL registryURL = null;
                for (URL url: urls) {
                    invokers.add(refprotocol.refer(interfaceClass, url));
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url;
                    }
                }
                if (registryURL != null) {
                    // 如果注册中心链接不为空，则将使用 AvailableCluster
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                } else {
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
           }

           // 生成服务代理类
           return (T) proxyFactory.getProxy(invoker);
}`, `89648564967083440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isJvmRefer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 生成本地引用 URL，使用 injvm 协议进行本地调用&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCAL_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NetUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCALHOST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refprotocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// URL 不为空，执行点对点调用&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 加载注册中心 URL&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 单个服务提供者，直接调用&lt;/span&gt;
                invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refprotocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 多个服务提供者&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; registryURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;URL url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;refprotocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaceClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REGISTRY_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        registryURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registryURL &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 如果注册中心链接不为空，则将使用 AvailableCluster&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; u &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; registryURL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLUSTER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AvailableCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StaticDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StaticDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invokers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

           &lt;span class=&quot;token comment&quot;&gt;// 生成服务代理类&lt;/span&gt;
           &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然 createProxy 方法的代码比较长，但它的执行逻辑还是比较清晰的。首先我们根据配置检查是否为本地调用，如果是则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例；如果不是，则读取 URL 配置项，包括用于直联的 URL 或基于注册中心的 URL。&lt;/p&gt;
&lt;p&gt;然后，我们对 URL 对象数量进行判断。如果 URL 数量为 1，则直接通过 Protocol 构建 Invoker 对象；如果 URL 数量大于 1，即存在多个服务地址，此时先根据每个 URL 构建 Invoker，然后再通过集群对象 Cluster 合并多个 Invoker，最后调用 ProxyFactory 生成代理类。&lt;/p&gt;
&lt;p&gt;这个过程实际上完成了两个步骤，首先是创建 Invoker 对象，然后才是生成服务代理类，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-29-25-29d805b6dae44431ade05392b8700431-73362.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 609px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 34.8111658456486%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABXElEQVQoz22R7U+CUBTG+f//haZba6u2PuSnasspKKKpSz9YiiIICILazBcQ5OXpeCmrzbM93HHvc38757lcmoKKfbDfh5jPlzCMKRzHxWazg+8HOFeZd8G8tu1gt/NpLwCXZkQ4Mwu6LmBqVjAaFjGUn2FbIjSVh2UZiOOYLuwRhgf0eh3mU8j3/vZIeoKh86QauCRJGNC2DdooY6KVINVuURWu2b+mlqCqI3iej/X6k7reotttElAg0AM6rwUmTS2SXwT3d4w4TrFYLKl9jwFcd44kSc+O7HlZt4dDxHQc9xgDFwQRtlsfx3VqzWg853TJdZcUg0k5hgzgeVmePzGdK04eVKFPRKhjEa1mATJlZ1kDmKaCscLjpXEPZSTQeYXikLBafXxDEwbO4L/i6tIN2q0r8OULVIQ86lIO8qCGfr+NhpSnPC8pzxwEOu927uj17X/A4xtEUXTSF5mrCEFtcS8+AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 29 25&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-29-25-29d805b6dae44431ade05392b8700431-73362.png&quot; data-srcset=&quot;/static/2024-10-28-18-29-25-29d805b6dae44431ade05392b8700431-06e0b.png 200w,
/static/2024-10-28-18-29-25-29d805b6dae44431ade05392b8700431-70531.png 400w,
/static/2024-10-28-18-29-25-29d805b6dae44431ade05392b8700431-73362.png 609w&quot; data-sizes=&quot;(max-width: 609px) 100vw, 609px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;显然，上述流程中，我们需要重点关注服务引用过程中 Invoker 对象的构建过程。那么问题来了，这里的这个 Invoker 是从何而来呢？实际上，Invoker 的构建过程是在 Protocol 中。与服务暴露的讲解思路一样，我们将从 DubboProtocol 这个 Protocol 的 refer 方法入手，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;93905073139007030000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Invoker&lt;T&gt; refer(Class&lt;T&gt; serviceType, URL url) throws RpcException {
        DubboInvoker&lt;T&gt; invoker = new DubboInvoker&lt;T&gt;(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);
        return invoker;
}`, `93905073139007030000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; serviceType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;DubboInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DubboInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getClients&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里出现了一个 getClients 方法，该方法用于获取客户端实例，实例类型为 ExchangeClient。我们已经在介绍 Dubbo 客户端通信原理时介绍过 ExchangeClient，可以参考前面做一些回顾。在理解了 getClients 方法之后，我们发现 DubboProtocol 的 refer 方法的作用就是返回一个新建的 DubboInvoker。&lt;/p&gt;
&lt;p&gt;DubboInvoker 继承了 AbstractInvoker，而 AbstractInvoker 实现了 Invoker 接口。AbstractInvoker 是一个抽象的模板方法类，提供了一个 doInvoke 模板方法。我们来看 DubboInvoker 中如何实现了这个模板方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;3858792534745481000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            if (isOneway) { // 单向调用
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) { // 异步调用
                ResponseFuture future = currentClient.request(inv, timeout);
                RpcContext.getContext().setFuture(new FutureAdapter&lt;Object&gt;(future));
                return new RpcResult();
            } else { // 同步调用
                RpcContext.getContext().setFuture(null);
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
}`, `3858792534745481000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doInvoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RpcInvocation&lt;/span&gt; inv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcInvocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        inv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttachment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PATH_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        inv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttachment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;VERSION_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;ExchangeClient&lt;/span&gt; currentClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            currentClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; clients&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            currentClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; clients&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAndIncrement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isAsync &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isOneway &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isOneway&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; timeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TIMEOUT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEFAULT_TIMEOUT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isOneway&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 单向调用&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isSent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SENT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                currentClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isSent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;RpcContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isAsync&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 异步调用&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;ResponseFuture&lt;/span&gt; future &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;RpcContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FutureAdapter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;future&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 同步调用&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;RpcContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; currentClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimeoutException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，Dubbo 的远程调用存在三种调用方式，即单向调用、异步无返回以及异步转同步。上述方法就包含了这三种调用方式的实现过程，而这些远程调用最终都是通过 ExchangeClient 进行完成。&lt;/p&gt;
&lt;p&gt;在 Dubbo 提供的这三种远程调用方式中，异步转同步是默认的实现方式。接下来，我们重点对这一过程做具体展开。&lt;/p&gt;
&lt;h3 id=&quot;服务调用异步转同步过程&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E5%BC%82%E6%AD%A5%E8%BD%AC%E5%90%8C%E6%AD%A5%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务调用异步转同步过程&lt;/h3&gt;
&lt;p&gt;在介绍 Dubbo 中异步转同步的服务调用方式之前，我们先围绕 JDK 中的 Future 模式讨论如何实现异步调用。&lt;/p&gt;
&lt;p&gt;Future 模式是对传统调用模式的一种改进，它们之间的对比可以参考下图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-10-28-18-30-33-8ff8a8d88c968f72e7dc7b054c9606e1-037b2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 499px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 102.00400801603206%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACAUlEQVQ4y6VUa2/aQBD0//8n+VJURShNWrWJVAXST26Ki4FAEOZlwIB5g8HTmyXnHNStIFlpJfvudnZ29vasfO4DPO8bCr8u8dP+iJJ7hefaDRwnD1ocx4nvdjtZK5dtdS4rMfncBXL3F6hVb1D8/RnW4+MllvMfaHpfUK9dw+/eIRx/h+s+JIDa4ngP+PRkq3Nfld8qoE+oVq9UzD28xh2swWCAXq+nNn10u12MhkP5XywWCdBms8F6vU7AV8ulnBmNRuh0OgiCPUYwCGDpIJYzHoeIokgAVqsVlipwOp2i0WioMssYBoGs06NoK0mbzSZMs3QZPOQ4DmazWeLz+VycgXT9/7o/Q6FQUAyDhJSly2BJLP9cGyqJWInWOwFkqXRmoR49X+nq+6LRdrv9q+M6ztT6peRXhpVKRej3+320Wi1MJhNZ19flsOP7b0pg7h8wLBaLyGQyCMNQNWgszTGZmZYGeMCQZRFQC3xc4tkM2WUCmtnSGJ3MkOVpgXn36Cyb/iYNKb7neXIF2u026vW6NIfXwuzyyYBkyEuazWahx5FJ3tUU13XVoFflAP+P753ZnJMYcvTMbMfM0sA5gv/sMvVKM804zRhnJj242Bw1Tod+aaghnyi+NqVSSbTlGvfoPMc1xqZ22bZtmRLzZfmfE5CaM2lqyWR4rjGG4BrwDx7eDjmOA8KkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 10 28 18 30 33&quot; title=&quot;&quot; data-src=&quot;/static/2024-10-28-18-30-33-8ff8a8d88c968f72e7dc7b054c9606e1-037b2.png&quot; data-srcset=&quot;/static/2024-10-28-18-30-33-8ff8a8d88c968f72e7dc7b054c9606e1-fd89b.png 200w,
/static/2024-10-28-18-30-33-8ff8a8d88c968f72e7dc7b054c9606e1-a94ea.png 400w,
/static/2024-10-28-18-30-33-8ff8a8d88c968f72e7dc7b054c9606e1-037b2.png 499w&quot; data-sizes=&quot;(max-width: 499px) 100vw, 499px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;本质上，Future 模式为我们提供了一种无需等待的服务调用机制。当我们发起一次服务调用时，Future 机制可以直接返回并继续执行其他任务，而不是像传统调用模式那样一直需要等到调用方法的返回。&lt;/p&gt;
&lt;p&gt;JDK 对 Future 模式提供了内置的实现，表现为如下所示的 Future 接口：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;20691430967769730000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Future&lt;V&gt; {
   // 取消执行
   boolean cancel(boolean mayInterruptIfRunning);
   // 判断是否已取消
   boolean isCancelled();
   // 判断是否已完成
   boolean isDone();
   // 等待任务执行完毕并获取结果
   V get() throws InterruptedException, ExecutionException;
   // 基于一定的超时时间等待任务执行完毕并获取结果
   V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}`, `20691430967769730000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 取消执行&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; mayInterruptIfRunning&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 判断是否已取消&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isCancelled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 判断是否已完成&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isDone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 等待任务执行完毕并获取结果&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutionException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 基于一定的超时时间等待任务执行完毕并获取结果&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt; unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExecutionException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeoutException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dubbo 中大量使用了基于 Future 机制的异步调用过程，同时也提供了异步转同步的实现机制，这是 Dubbo 提供的这三种远程调用方式中默认的实现方式。这部分内容实际上已经超出了服务引用的范围，而是更多偏向于讨论底层的网络通信，所以需要你对网络通信相关的内容先进行学习和掌握。&lt;/p&gt;
&lt;p&gt;在 DubboInvoker 中 doInvoke 方法中，异步转同步过程的实现如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50553361763407050000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();`, `50553361763407050000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; currentClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们先来看这里的 request 方法定义（位于 HeaderExchangeChannel 类中），如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50656113444733640000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public ResponseFuture request(Object request, int timeout) throws RemotingException {
   Request req = new Request();
   req.setVersion(&amp;quot;2.0.0&amp;quot;);
   req.setTwoWay(true);
   req.setData(request);
   DefaultFuture future = new DefaultFuture(channel, req, timeout);
   try {
      channel.send(req);
   } catch (RemotingException e) {
      future.cancel();
      throw e;
   }
   return future;
}`, `50656113444733640000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResponseFuture&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt; req &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTwoWay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;DefaultFuture&lt;/span&gt; future &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      future&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; future&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，这里用于发送请求的 channel.send 方法是异步执行的，也就说该方法一旦调用就会直接返回。为了实现“异步转同步”，Dubbo 在这里使用了 DefaultFuture 这个辅助类。请记住这个类，我们在后续内容中还会再次提到该类。&lt;/p&gt;
&lt;p&gt;另一方面，我们在介绍网络通信时也提到，当请求到达服务器端时，在 NettyServer 中会使用一个 NettyHandler 作为网络事件的处理器，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69614283139467740000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`pipeline.addLast(&amp;quot;handler&amp;quot;, nettyHandler);`, `69614283139467740000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;pipeline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;handler&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nettyHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;NettyHandler 是一个接口，我们来看它的 messageReceived 方法实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;93024060064802130000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private final ChannelHandler handler;

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.received(channel, e.getMessage());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
}`, `93024060064802130000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;messageReceived&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ChannelHandlerContext&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageEvent&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;NettyChannel&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NettyChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrAddChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;received&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;NettyChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeChannelIfDisconnected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里把具体的处理逻辑转移到了 Dubbo 中自定义的 ChannelHandler 接口，这个接口有很多实现类，也包括 ChannelHandlerDelegate 这个代理类，而真正处理事件接收逻辑的 HeaderExchangeHandler 正是实现了这个代理类。HeaderExchangeHandler 中处理响应的实现过程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;99376395366925620000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`static void handleResponse(Channel channel, Response response) throws RemotingException {
        if (response != null &amp;&amp; !response.isHeartbeat()) {
            DefaultFuture.received(channel, response);
        }
}`, `99376395366925620000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isHeartbeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;DefaultFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;received&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们在这里再次看到了 DefaultFuture，这里的 DefaultFuture 就是前面客户端发送请求时用到的 DefaultFuture。DefaultFuture 的 received 方法中有进一步调用了如下所示的 doReceived 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19347272777502390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void doReceived(Response res) {
        lock.lock();
        try {
            // 设置响应 response 对象
            response = res;
            if (done != null) {
               // 唤醒阻塞的线程
               done.signal();
            }
        } finally {
            lock.unlock();
        }

        if (callback != null) {
            invokeCallback(callback);
        }
}`, `19347272777502390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doReceived&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 设置响应 response 对象&lt;/span&gt;
            response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 唤醒阻塞的线程&lt;/span&gt;
               done&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;invokeCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意到这里的 done.signal 方法的执行效果会唤醒阻塞的线程，那么这个阻塞的线程在哪里的？显然，这时候我们应该回到客户端组件看看同步获取调用结果的入口。&lt;/p&gt;
&lt;p&gt;我们再次回到在 DubboInvoker 中 doInvoke 方法中，看到了如下所示的核心代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;6776035056237872000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();`, `6776035056237872000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; currentClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们来具体看一下这个获取调用结果的 get 方法执行逻辑，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18041013607471946000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Object get(int timeout) throws RemotingException {
        if (timeout &lt;= 0) {
            timeout = Constants.DEFAULT_TIMEOUT;
        }
        if (!isDone()) {
            long start = System.currentTimeMillis();
            lock.lock();
            try {
                while (!isDone()) { // 当响应 response 对象为空
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    if (isDone() || System.currentTimeMillis() - start &gt; timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            if (!isDone()) {
                throw new TimeoutException(sent &gt; 0, channel, getTimeoutMessage(false));
            }
        }
        return returnFromResponse();
}`, `18041013607471946000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemotingException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            timeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEFAULT_TIMEOUT&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 当响应 response 对象为空&lt;/span&gt;
                    done&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeoutException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sent &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTimeoutMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;returnFromResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，当响应 response 对象为空时，Condition 会执行 await 方法来阻塞当前线程，直到该线程被唤醒、被中断或超过阻塞时间。而在前面所述的 DefaultFuture 类的 doReceived 方法中，我们也看到会先为 response 赋上返回值，之后执行 Condition 的 signal 方法唤醒被阻塞的线程，这样 get 方法就会释放锁，进而执行 returnFromResponse 方法来返回值。&lt;/p&gt;
&lt;p&gt;这样，整个远程调用的异步转同步过程就介绍完毕。作为总结，我们明确 Dubbo 异步转同步的原理其实就是利用 Lock 和 Condition 实现了等待通知机制。当客户端发送请求时，将一个请求 Id 和一个 DefaultFuture 对象包装在请求对象中。而当客户端异步收到响应时，则根据这个请求 Id 从响应结果中获取对应的 DefaultFuture 对象，并调用该 DefaultFuture 对象的 get 方法获取最终的调用结果。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-6&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;在涉及到远程调用的应用场景，很多开源框架都会基于 Future 或它的一些变种，例如 JDK 自身提供的改进版 CompleteFuture，或是 Google 的 guava 框架中提供的 ListenableFuture 等。类似的问题主要还是关注 Future 机制本身的一些特性，可以发散出一系列的问题，但基本的考点是一致的，回答的思路也类似。&lt;/p&gt;
&lt;p&gt;Future 机制本身提供的几个接口也并不复杂，需要理解它们的含义和作用，但也要理解它们存在的不足。普通 Future 机制的最大问题在于没有提供通知的机制，也就是说我们不知道 Future 什么时候能够完成。前面提到的 CompleteFuture 和 ListenableFuture 实际上都是为了改进普通 Future 存在的这一问题而诞生的。本讲内容对 Future 的概念做了类比介绍，同时给出了 JDK 中 Future 接口的各个核心方法。通过掌握这些核心方法，针对这个问题我们就能拿到 60 分。如果我们还能够进一步分析基本 Future 机制的不足，然后引出 CompleteFuture 或 ListenableFuture 等改进版本的 Future，那么拿到 80 分就不成问题。&lt;/p&gt;
&lt;p&gt;另一方面，对于 Dubbo 框架中的服务引用过程，我们需要重点掌握的是它的三种调用方式，即单向、同步和异步。其中前面两种比较好理解，而针对异步，我们在使用 Dubbo 的过程中实际上最终也是转换为同步操作。针对这一问题，如果只是回答这个问题中所提出的实现方式的种类，那么只要简单列举即可。但要说明具体的实现细节，尤其是 Dubbo 中 &lt;code class=&quot;language-text&quot;&gt;异步转同步&lt;/code&gt; 的实现细节，那么需要对本讲内容做深入的理解，并尝试使用自己的语言来总结整个过程。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-4&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;承接上一讲内容，本讲围绕远程过程调用的服务引用过程展开了讨论。同样的，基于这套服务引用流程，我们也对 Dubbo 中服务引用的底层设计思想和实现过程进行了分析，尤其对服务调用异步转同步过程做了详细的阐述。&lt;/p&gt;
&lt;p&gt;事实上，关于服务引用的完整介绍还没有结束。相较服务发布，服务引用还涉及到负载均衡、服务容错等技术组件，这些技术组件都是构建分布式服务所必不可少的。在接下来的几讲内容中，我们将一一对这些技术组件展开讨论。下一讲内容将先从负载均衡进行切入，在服务发布和引用的基础上回答这样一个问题：负载均衡如何与远程调用过程进行整合？我们下一讲详聊。&lt;/p&gt;
&lt;h1 id=&quot;负载均衡：负载均衡如何与远程调用过程进行整合？&quot;&gt;&lt;a href=&quot;#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%EF%BC%9A%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%A6%82%E4%BD%95%E4%B8%8E%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B%E8%BF%9B%E8%A1%8C%E6%95%B4%E5%90%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;负载均衡：负载均衡如何与远程调用过程进行整合？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们详细阐述了在远程调用过程中服务引用的流程和实现方式。请注意，在现实环境中，我们一般都会采用集群模式对服务进行多实例化的部署，以防止出现单点故障。&lt;/p&gt;
&lt;p&gt;那么，问题就来了，服务消费者的每一次远程调用就需要确定对这些服务实例中的具体哪一个发起请求，这就是负载均衡要解决的问题。&lt;/p&gt;
&lt;p&gt;负载均衡（Load Balance）是一个复杂的话题，要想在远程调用过程中引入负载均衡，我们首先需要回答一个基本的问题，即：负载均衡是如何与远程调用过程进行整合的呢？本讲内容将围绕这一问题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-4&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;相信你对集群这个概念并不陌生。在分布式系统开发过程中，集群的作用主要有两点，一方面通过服务的冗余为系统可用性提供了一种技术手段，另一方面也针对系统的性能问题提供了解决方案。通过集群，我们能够将业务请求分摊到多台单机性能不一定非常出众的服务器上。这部分内容作为负载均衡的基本概念，是每位面试候选人都应该掌握的。&lt;/p&gt;
&lt;p&gt;但是，如果问到“如何在远程调用中集成负载均衡机制”这个问题，就我的面试经验，相信很多候选人就不一定能回答出来了。从问题本身而言，你可能会觉得负载均衡和远程调用是天生整合在一起的，因为在日常开发过程中，开源框架以及运行时环境都已经帮你准备好了这部分工作，普通开发人员不需要参与，这也导致了我们对这部分内容不够重视。但是，就技术实现而言，两者之间的集成过程又非常重要。可以说，正是因为能够在远程调用过程中集成负载均衡机制，才会有我们后续要介绍的集群容错、服务熔断等一系列技术组件的应用。&lt;/p&gt;
&lt;p&gt;进一步，我们来梳理针对这一话题的常见面试提问方式，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;负载均衡的基本结构是怎么样的，它有什么作用？&lt;/li&gt;
&lt;li&gt;如果想要在远程调用过程中嵌入负载均衡机制，你有什么设计思路？&lt;/li&gt;
&lt;li&gt;你能简要描述 Dubbo 框架的负载均衡组成结构吗？&lt;/li&gt;
&lt;li&gt;在 Spring Cloud 中，为什么在 RestTemplate 类上添加了 @LoadBalanced 注解，就能自动集成负载均衡功能？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于这类问题，我们注意到问题的要点并不在于负载均衡本身的概念和原理，而更多的是关于具体的实现过程和机制，所以是一种偏实战类的考查方式。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-5&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;在分布式系统构建过程中，势必需要引入负载均衡机制，而业界关于如何实现负载均衡存在一系列工具。在这些工具中，有些提供的是偏向于底层负载均衡算法实现的工具库，而有些则提供了整套负载均衡实现方案。无论是哪种类型，我们都需要获取想要访问的目标服务当前实例信息，这是实现远程调用和负载均衡进行整合的前提，也是我们在回答这类问题时的第一个要点。&lt;/p&gt;
&lt;p&gt;回答这类问题的第二个要点在于，我们以什么样的技术手段完成在远程调用链路中自动嵌入负载均衡机制。从系统架构设计角度，这个嵌入过程应该对开发人员透明，且应该是可扩展的。在这点上，不同的实现框架有不同的策略，常见的包括两大类。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于拦截机制：通过类似 AOP 的实现机制对请求进行拦截，再应用动态代理机制完成对负载均衡机制的嵌入。&lt;/li&gt;
&lt;li&gt;基于集群机制：在集群构建过程中完成对负载均衡机制的嵌入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后，正如问题背景部分所阐述的，“如何在远程调用中集成负载均衡机制”这类话题考查的并不是概念，而是一种实践能力。我们通常不会自己去完成这个集成过程，但对于主流开源框架中的做法需要非常熟悉，否则很难从实现机制和过程上对这类问题进行解答。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-7&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;前面提到有两大类技术手段可以完成对远程调用和负载均衡的集成，事实上这也是 Spring Cloud 和 Dubbo 这两个主流分布式服务框架所采用的实现方式。本讲内容将讨论第一种，即拦截机制。关于集群机制的分析我们放在之后再展开。&lt;/p&gt;
&lt;p&gt;如果我们采用拦截机制，那么整体的设计思想可以用下图进行展示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-04-10-59-23-0064dc22aa6ba04b729c7ed093939108-9a912.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 481px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.20166320166321%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACIElEQVQ4y5VTiVLaUBTN//9GdVyoFcVBKQNUVkVBQMKSDYSEJISRLEDYktObTFu0lc54Z+7kJXnn3HveuY9ps5e4Kx2hfH+Cx/IpGvVvEAQWQXi+B9/3P5XMWMug9hQhojO02HNMjAw4rhES+kT42WAURYYoCUTSgaarGA4HWK/XvwjfV3/7jVZ//fPDBhhzOkWvJ6JarcCyTBjGGJvNZgeGv4f43ww7tMwCBO4K1coxVCWFkZJEt7uT/BHZYCCg2ylD4KuUlTC7nQdIPQ6MMc6DbcaQyx6gJyZpc4pMae09Q8uakZoYDD2OWvUAIn+GRu0Qjp3Bzc0Xkmw6GI00IpJh23O8vlrvCDzPC890sVhguVzSXhWVxzx1VCIzf+C5kSVDs6SqBJ5nA1N6aLdrKBQyUNUX9PscTNOE67qYz+fhc7VaYbvdhuRBTCYGNSBB02QqMCDckHAipuQHs3SL4LtRtJpfoatJ2NYtms2HELhaLQk4gCiKNAVcCCBeNJ8T0NRr6u4YkniBNhuBZWaQTB6BEYVb3N9dopA/p43fCZim0ZH+yHUcJ0zbtsMCgS88mdBpp+gyXJHsBB1BnIhzqDdKYFx3TWPiUWUfq/UWwfvvkdkXluVAUZRQuq7rNGoGZFnGbDYH8xFg37UKYrPxSWoKfSlG3Z2C60RRfzqBIieQTkfA/A/8tssdoUeOFjF4SRHhBUm/pvGJQtdyKBbj+An+wszvchaIjgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 04 10 59 23&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-04-10-59-23-0064dc22aa6ba04b729c7ed093939108-9a912.png&quot; data-srcset=&quot;/static/2024-11-04-10-59-23-0064dc22aa6ba04b729c7ed093939108-06486.png 200w,
/static/2024-11-04-10-59-23-0064dc22aa6ba04b729c7ed093939108-fe61f.png 400w,
/static/2024-11-04-10-59-23-0064dc22aa6ba04b729c7ed093939108-9a912.png 481w&quot; data-sizes=&quot;(max-width: 481px) 100vw, 481px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图所展示的基本思想在于：当我们采用拦截机制对远程方法进行拦截时，并不是直接执行该方法中的业务逻辑，而是嵌入自定义的一套负载均衡机制。这套负载均衡机制能够获取当前所有可用的服务实例信息，并基于一定策略确定目标服务实例。这个过程对开发人员是透明的。&lt;/p&gt;
&lt;p&gt;现实中，采用这种实现机制的代表性框架就是 Spring Cloud。如果你使用过 Spring Cloud，那你应该知道想要在服务调用过程中嵌入负载均衡机制，要做的事情只有一件，就是在 RestTemplate 模板工具类上添加一个 @LoadBalanced 注解，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;73042280945915960000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
     return new RestTemplate();
}`, `73042280945915960000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@LoadBalanced&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们知道 RestTemplate 是 Spring 自带的一个 HTTP 请求工具类，本身并不具备负载均衡能力。讲到这里，你可能会觉得奇怪，为什么在这个工具类上添加了 @LoadBalanced 注解就能自动嵌入负载均衡机制呢？这个 @LoadBalanced 注解背后的工作原理又是怎么样的呢？为了回答这些问题，我们需要深入分析 Spring Cloud 的相关源码。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-5&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;让我们打开 Spring Cloud 源码，来到 spring-cloud-commons 这个代码工程，可以发现虽然这个工程的名称是 common，但内置了大量以 client 命名的代码包。这些代码包中就包含了与服务发现、负载均衡相关的所有基础类定义。我们要介绍的 @LoadBalanced 注解也位于这些代码包中。&lt;/p&gt;
&lt;p&gt;接下来，让我们先从这个注解开始讲起。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-中的-loadbalanced&quot;&gt;&lt;a href=&quot;#spring-cloud-%E4%B8%AD%E7%9A%84-loadbalanced&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud 中的 @LoadBalanced&lt;/h3&gt;
&lt;p&gt;事实上，在 Spring Cloud 中维护着一个 RestTemplate 模板工具类的列表，而在该列表上就嵌入了 @LoadBalanced 注解，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11109393088043106000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@LoadBalanced
@Autowired(required = false)
private List&lt;RestTemplate&gt; restTemplates = Collections.emptyList();`, `11109393088043106000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@LoadBalanced&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;required &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; restTemplates &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emptyList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码位于自动配置类 LoadBalancerAutoConfiguration 中。针对这些被 @LoadBalanced 注解修饰的 RestTemplate，在 LoadBalancerAutoConfiguration 初始化的过程中会调用 RestTemplateCustomizer 的 customize 方法进行定制化，这个定制化的过程就是对目标 RestTemplate 增加拦截器 LoadBalancerInterceptor，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58363661714380740000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
       return restTemplate -&gt; {
                List&lt;ClientHttpRequestInterceptor&gt; list = new ArrayList&lt;&gt;(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                // 为 RestTemplate 添加拦截器
                restTemplate.setInterceptors(list);
       };
}`, `58363661714380740000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RestTemplateCustomizer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;restTemplateCustomizer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerInterceptor&lt;/span&gt; loadBalancerInterceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; restTemplate &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ClientHttpRequestInterceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInterceptors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadBalancerInterceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 为 RestTemplate 添加拦截器&lt;/span&gt;
                restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setInterceptors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里就用到了 RestTemplate 的拦截器扩展机制。通过这种机制，我们可以在 RestTemplate 发送请求的过程中添加定制化的功能。从命名上看，这里的 LoadBalancerInterceptor 就是添加了负载均衡的拦截器，我们在它的构造函数中发现传入了一个 LoadBalancerClient，而在它的拦截方法中本质上就是使用这个 LoadBalanceClient 来执行真正的负载均衡，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;56319346699814770000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
   // ...
   public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
      this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
   }

   @Override
   public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
      final URI originalUri = request.getURI();
      String serviceName = originalUri.getHost();

      // 通过 LoadBalancerClient 执行负载均衡
      return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
   }
}`, `56319346699814770000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerInterceptor&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpRequestInterceptor&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerInterceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LoadBalancerClient&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerRequestFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpRequestExecution&lt;/span&gt; execution&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; originalUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; originalUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 通过 LoadBalancerClient 执行负载均衡&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; execution&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的拦截方法 intercept 直接调用了 LoadBalancerClient 接口的 execute 方法完成对请求的负载均衡执行。而该方法的输入参数有两个，一个是代表服务名称的 serviceName，另一个则是代表负载均衡请求对象的 LoadBalancerRequest。而具体的 LoadBalancerRequest 则是如下所示的一个 ServiceRequestWrapper 包装类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;83432476480514330000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ServiceRequestWrapper extends HttpRequestWrapper {
   private final ServiceInstance instance;

   private final LoadBalancerClient loadBalancer;

   public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
      super(request);
      this.instance = instance;
      this.loadBalancer = loadBalancer;
   }

   @Override
   public URI getURI() {
      URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
      return uri;
   }
}`, `83432476480514330000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceRequestWrapper&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpRequestWrapper&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerClient&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceRequestWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerClient&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loadBalancer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reconstructURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里同样出现了 LoadBalanceClient，并用它来完成了请求地址 URI 的构建。显然，LoadBalanceClient 是我们分析负载均衡机制的核心入口。接下来，我们就对该接口及其实现类进行详细的展开。&lt;/p&gt;
&lt;h3 id=&quot;loadbalanceclient-接口与实现类&quot;&gt;&lt;a href=&quot;#loadbalanceclient-%E6%8E%A5%E5%8F%A3%E4%B8%8E%E5%AE%9E%E7%8E%B0%E7%B1%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;LoadBalanceClient 接口与实现类&lt;/h3&gt;
&lt;p&gt;LoadBalancerClient 是一个非常重要的接口，定义如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5870506979596613000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface LoadBalancerClient extends ServiceInstanceChooser {
   // 执行负载均衡调用
   &lt;T&gt; T execute(String serviceId, LoadBalancerRequest&lt;T&gt; request) throws IOException;

   // 执行负载均衡调用
   &lt;T&gt; T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest&lt;T&gt; request) throws IOException;

   // 构建负载均衡调用 URI
   URI reconstructURI(ServiceInstance instance, URI original);
}`, `5870506979596613000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerClient&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstanceChooser&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 执行负载均衡调用&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerRequest&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 执行负载均衡调用&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; serviceInstance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalancerRequest&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 构建负载均衡调用 URI&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reconstructURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; original&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意到这里有两个 execute 重载方法，用于根据负载均衡器所确定的服务实例来执行服务调用。而 reconstructURI 方法 则用于构建服务 URI，基于负载均衡所选择的 ServiceInstance 信息，并利用服务实例的 host、port 以及端点路径，我们就可以构造出一个真正可供访问的服务地址。&lt;/p&gt;
&lt;p&gt;同时，我们发现 LoadBalancerClient 有一个父接口 ServiceInstanceChooser，该接口定义如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;7297066196614410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ServiceInstanceChooser {
   // 根据 serviceId 选择目标服务实例
   ServiceInstance choose(String serviceId);

   // 根据 serviceId 和请求选择目标服务实例
   &lt;T&gt; ServiceInstance choose(String serviceId, Request&lt;T&gt; request);
}`, `7297066196614410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstanceChooser&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 根据 serviceId 选择目标服务实例&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;choose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 根据 serviceId 和请求选择目标服务实例&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;choose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，从负载均衡角度讲，我们应该重点关注实际上是这两个 choose 方法的实现，因为它们完成了对目标服务实例的具体选择过程，而这个选择过程集成了各种负载均衡算法。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud 中，LoadBalancerClient 接口有一组实现类。我们接下来要介绍的是 Spring Cloud Netflix 中的 RibbonLoadBalancerClient 类，该类基于 Netflix Ribbon 组件实现了负载均衡机制，是 Spring Cloud 中最早、也是最经典的一种负载均衡实现方式。&lt;/p&gt;
&lt;p&gt;这里有必要梳理一下 Netflix Ribbon 和 Spring Cloud 之间的关系。我们知道 Netflix Ribbon 是来自 Netflix 的一个外部组件，它提供的只是一个辅助工具，这个辅助工具的目的就是让你去集成它，而不是说它自己完成所有的工作。而 Spring Cloud 中的 Spring Cloud Netflix Ribbon 专门针对 Netflix Ribbon 提供了一个独立的集成实现。对于 Netflix Ribbon 而言，Spring Cloud Netflix Ribbon 相当于它的客户端，而对于 Spring Cloud Netflix Ribbon 而言，我们的应用服务相当于它的客户端。&lt;/p&gt;
&lt;p&gt;Netflix Ribbon、Spring Cloud Netflix Ribbon、应用服务这三者之间的关系以及核心入口如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-04-11-02-11-084e5c12e53da667b08c95bfcf82efe5-3e45f.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 371px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 128.03234501347708%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsSAAALEgHS3X78AAAEvElEQVRIx41VaXMaRxDdX5+qVCWVlCPFTtmRLSOEEPeNQEIIlmuXY2E5BLovdCJhLNuxHfvlTSMptj9pq171HD2z093vzWi6Hodp+GAY/jvrE2uyX6l4oetLyOddyOUWkc8toVL2fuenrFHxoVr1c34Zmml4AWwRPWLzG/SJBg72YtjdiWKwGcJWP4gv/1Y5PvjBvyd7jK6yakM/Oy1cnGdxdbnxgOtRXuzlRfbB3rdHVzmMLnMPvmrtx39MnJ1loFmNMA73Ezz+InrdADptP9q2D/WqC3bLK6iZLjStZbSaHrSUtTwy3u34BWrOKDv5w3VobTuOrUEYkzdFnrQreDspYSXxDNtbMW7mRiHvQKm4wJ8so9cJ07rx/p1x59+WCK2GR06u6Uz40WEUqZUZZNaeYT3zDOnULIKBX2hnkEpOx9OpP8VnNT175/tUsM451a+aDh4sCG0waKPbLTPcCkMtod0uMeyS9O+t1cihVsui1zOm852y2G/R75uw7Qo0POIbjSbY3T16jCs0266SW+SimeIp0mjUV2FZawwhJbBbWYbmRSj0kuMZ1Kpp+idQp6/VWEOd/qaxQiTpz6LoupP71nF6ssLkz3FDVrSxhM2ujwVRhXBiZysMs7zAfD6FxXnFgPzGHCv7mkXy4sP7Er58NlnEMLSq6RWifv1iCbdu35aFW5M3JbwZF6VyH94bbBdwc60LAz59rAlPx+xPq90WIZwcr0Jr1P24GeVQLMwzBDdPuMTieAWdtuKaj2F7BGrMVlxsTvvKt15zydrdnRhOh9yw1QqJ4/gmz7/Y+Pyphj1OdjsB5mSJG3Dzlh872zFKL0wJRtBtB7C/m6B/k7AYXZ2+izg+SkFrWkFRSp9aPR1mcLC/IhsVC1MyF/LzKOoOIXjVcDEKL8oct5s+bpDGkGGqNUpdw5M0Q24EmJsc3K6fEQr+hkjod8RjfyARmxGsJGYFyfi0H4s+ERuNPEGYvgHfr1hc+EkiOT+llu1WmBUNMLkVVjPOkKjtgySOD9Pi0Ge/xdMou7+XkBMdHqR4qiTTEJciqYJY9WWc8MSaaXoovRgF74ZRckDPzaHEJFfYrhRfI5d9gQ2F9Rco6C9lzqwskDIOzv3NfPqwPQgxFS7+KKFOaKJMYoeCCwzBiUhkEZGwU9oKsaiLcw4E/PPSfpijjdI3GHDA63mF3EZI5Pko6Y3HtxgOLx4nve3tHsVuEOZ36PfrGAwa1LDNJyDJQi2Tay0Z6yqftvENTNmjv9mEll2fZ0F0kjtLLm5QEepGzqBRczJvz0WOTeanqL+ijhflmro4X8VknBN/hZvrLN7d6kIdKsV396Y079ASq677PZJXcU21j49WSeYk35ikkP/eT/C1KfIdnqwpHvpxOyny5XJSSm5KSWFJngJF9gHVoZ4GZe+hrn/lo3zVGrX26DA51XLbnkrv/ydA/bnDsNZxRh4qrnXsAG/jqHBPnfial8L0QrDv0OT15RR+kjYBLkzLJXB8qEKKi1X5ymae83p/irX0X2JVP8u81pRule9+XLi3sx2R9ednDLmghxhagjxSD7mLxOZdx0e9VHCjXPKQwF5eqLSVqVUoFd3ic++/kXVSTRGmIIL/ANjD9XUK4zzrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 04 11 02 11&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-04-11-02-11-084e5c12e53da667b08c95bfcf82efe5-3e45f.png&quot; data-srcset=&quot;/static/2024-11-04-11-02-11-084e5c12e53da667b08c95bfcf82efe5-84b18.png 200w,
/static/2024-11-04-11-02-11-084e5c12e53da667b08c95bfcf82efe5-3e45f.png 371w&quot; data-sizes=&quot;(max-width: 371px) 100vw, 371px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在 RibbonLoadBalancerClient 中，我们可以看到它的 choose 方法进一步调用了一个 getServer 方法来获取服务器信息，而这个 getServer 方法则是通过 ILoadBalancer 接口完成了对目标服务的选择，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;20854695700590043000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public ServiceInstance choose(String serviceId, Object hint) {
   Server server = getServer(getLoadBalancer(serviceId), hint);
   // ...
}

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
   // ...
   return loadBalancer.chooseServer(hint != null ? hint : &amp;quot;default&amp;quot;);
}`, `20854695700590043000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;choose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; hint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLoadBalancer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ILoadBalancer&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; hint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; loadBalancer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hint &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; hint &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个 ILoadBalancer 就来自于 Netflix Ribbon，该接口位于 com.netflix.loadbalancer 包下，定义如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;57901411685369220000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ILoadBalancer {
   // 添加后端服务
   public void addServers(List&lt;Server&gt; newServers);

   // 选择一个后端服务
   public Server chooseServer(Object key);

   // 标记一个服务不可用
   public void markServerDown(Server server);

   // 获取当前可用的服务列表
   public List&lt;Server&gt; getReachableServers();

   // 获取所有后端服务列表
   public List&lt;Server&gt; getAllServers();
}`, `57901411685369220000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ILoadBalancer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 添加后端服务&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addServers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; newServers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 选择一个后端服务&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chooseServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 标记一个服务不可用&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;markServerDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 获取当前可用的服务列表&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getReachableServers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 获取所有后端服务列表&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAllServers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;针对负载均衡，我们应该重点应该关注的是 ILoadBalancer 接口中 chooseServer 方法的实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62130489807668400000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                 return null;
            }
        }
}`, `62130489807668400000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chooseServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;counter &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createCounter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        counter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rule &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; rule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;choose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                 &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这里使用了一个 IRule 接口集成了具体负载均衡策略的实现。IRule 接口是对负载均衡策略的一种抽象，可以通过实现这个接口来提供各种负载均衡算法，该接口的类层结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-04-11-03-24-1a90a6a70112a57bf58d5ad6b75ea5ae-200d5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 474px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.89873417721519%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABx0lEQVQoz2WS6XKCMBSFef+nqtr6q506li1sFSyVHZRFReH03mDttM3MJZlkOPnuOVHWawuz+QNWqxWeX17wtHzCfDHHcrnEbPaAxeMCrueCxziOt3m4r/8OpahKGBsTuqFD01QIy8Lr6hWarlFNe2mWIUlTRHFE6xRFWSDPc1nn8xnX6xWXS091gTIOI+pjg10ewbYFPM+DRuJCGDCEwHYbwA82dGbDsi2EYYjD4YC8yFEUhVzv93uqCnVdQ5GcJPpZxbBdCzpRqZqGN3VNhKr8qW1bmnOiTJARVRTHSJIYXdf9b5k/jFx3DYJ4C9d1SFCVFjCl9+7B9zdIqWUm+RHOqOVMUh2PR+kplzKZOxmc1CmCwIcpzEmMwjAMg1r2722xMIuyIFOWlEHf9/eQJGHbdwh2ARzHlkLsVUnGcwifu90kQMHE1CpbwEFwACw0DMOvF6AwXNGWcAMXJtGo2ptsiUWqqiLhUorxHifcNM1d5O+QLfPhvj0gTEM4rg3TpHRNXc5MGEURJfuBDyqmZnF+Rk3bSMLT6SSJvy9Rxpt/OVO+O+SfoCdi0XsUJEzElLRBAbGvNdGxj+xfRrRMzD7yJWcS5/EFT1Ht1q4VkFAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 04 11 03 24&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-04-11-03-24-1a90a6a70112a57bf58d5ad6b75ea5ae-200d5.png&quot; data-srcset=&quot;/static/2024-11-04-11-03-24-1a90a6a70112a57bf58d5ad6b75ea5ae-4fa1a.png 200w,
/static/2024-11-04-11-03-24-1a90a6a70112a57bf58d5ad6b75ea5ae-57557.png 400w,
/static/2024-11-04-11-03-24-1a90a6a70112a57bf58d5ad6b75ea5ae-200d5.png 474w&quot; data-sizes=&quot;(max-width: 474px) 100vw, 474px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到 Netflix Ribbon 中的负载均衡实现算法非常丰富，既提供了 RandomRule、RoundRobinRule 等无状态的静态算法，又实现了 AvailabilityFilteringRule、WeightedResponseTimeRule 等多种基于服务器运行状况进行实时路由的动态算法。关于这些负载均衡算法的讨论我们放在下一讲中。&lt;/p&gt;
&lt;p&gt;在上图中还看到了 RetryRule 这种重试策略，该策略会对选定的负载均衡策略执行重试机制。严格意义上讲，重试是一种服务容错而不是负载均衡机制，但 Ribbon 也内置了这方面的功能。&lt;/p&gt;
&lt;p&gt;事实上，我们也可以基于 IRule 接口实现任何定制化的负载均衡算法，然后通过配置的方式加载到 Spring Cloud 中，示例代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82229289198813710000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Configuration
public class LoadBalanceConfig{

    @Autowired
    IClientConfig config;

    @Bean
    @ConditionalOnMissingBean
    public IRule customRule(IClientConfig config) {

        return new RandomRule();
    }
}`, `82229289198813710000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalanceConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;IClientConfig&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IRule&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;customRule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IClientConfig&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RandomRule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然该配置类的作用是使用 RandomRule 替换 Ribbon 中的默认负载均衡策略 RoundRobin。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-7&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于负载均衡和远程调用的整合过程是一道经典的面试题，理论知识和工程实践都有涉及，我经常拿这道题来考察候选人。就考查内容而言，这道题所涵盖的知识面要求很广，需要面试者对负载均衡、远程调用以及主流的分布式服务框架本身的功能特性都有一定的了解。&lt;/p&gt;
&lt;p&gt;而从提问方式上讲，这个面试主题往往会直接从具体的工具框架的特性进行切入，考查面试者对框架原理的理解，正如本讲中重点介绍的 Spring Cloud 和 @LoadBalanced 注解。乍一看，这类面试题感觉无从下手，因为 @LoadBalanced 注解在使用过程中并没有提供太多的配置项供开发人员进行设置，我们也就很难联想到其背后所具备的负载均衡机制。&lt;/p&gt;
&lt;p&gt;但事实上，在这个注解背后隐藏着一个很重要的技术组件，即拦截器。通过在某一个注解中添加拦截功能，然后把一些非功能性需求通过拦截器进行实现，这是 Spring 等优秀开源框架中所经常使用的一种开发技巧，也是面试官考查面试者对开源框架理解能力的一个常见维度。在回答这道面试题时，我们就需要从拦截器的角度出发，把 RestTemplate 和 Ribbon 组件结合起来进行分析。至于负载均衡算法本身，反而不是这道面试题考查的重点，我们只要点到即可。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-5&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;通过今天内容的介绍，我们明确作为一款负载均衡工具，要做的事情无非是从服务列表中选择一个服务进行调用。为了实现这个过程，我们需要提供入口供客户端请求进行使用。而 Spring Cloud 为我们提供了一种非常友好的实现方式，开发人员只需要通过一个简单的 @LoadBalanced 注解就能自动在调用过程中集成负载均衡机制。今天的内容对 @LoadBalanced 注解以及背后的整个负载均衡实现流程做了原理分析。通过这种方式，我们也加深了对“如何在远程调用中集成负载均衡机制”这个问题的理解程度。&lt;/p&gt;
&lt;p&gt;在前面的内容中，我们已经看到 Spring Cloud 内置了一组丰富的负载均衡算法，而这些负载均衡算法能够根据需要帮助我们自动找到最佳的目标服务。事实上，任何一款分布式服务框架都提供了各种不同类型的负载均衡算法供开发人员进行选择。那么，如何实现这些常见的负载均衡算法？这就是下一讲要讨论的内容。&lt;/p&gt;
&lt;h1 id=&quot;负载均衡：如何实现常见的负载均衡算法？&quot;&gt;&lt;a href=&quot;#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%B8%B8%E8%A7%81%E7%9A%84%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;负载均衡：如何实现常见的负载均衡算法？&lt;/h1&gt;
&lt;p&gt;上一讲我们分析了在远程调用过程中整合负载均衡机制的实现过程。在这种场景下，我们将多个服务实例集中在一起，每一次请求都可以由集群中的某一个服务实例进行响应。&lt;/p&gt;
&lt;p&gt;那么，具体某一个请求到底应该是由哪个服务实例来响应最为合适呢？这个话题看上去很简单，实际上却有点复杂，涉及到服务请求的路由机制。&lt;/p&gt;
&lt;p&gt;而在分布式系统中，上一讲中引入的负载均衡就是最常见也是最具代表性的一种路由机制。为了对请求进行合理的分发，我们需要提供一组负载均衡算法，那么常见的负载均衡算法有哪些？它们又应该如何实现呢？这是一道非常典型的面试题，本讲内容将围绕这一主题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-5&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;我们先来看这个问题背后的示意图，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-05-10-38-28-f71a59bfc24e16a2d9f20422e520fc12-92ba0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 636px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.35220125786164%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABgklEQVQoz3VRa0/CQBDs//8rahCEQkHiF1QwEGh5trSFFoSW2vKwUBPpjXtXo8aESyY3+7ydPUntyVi6dcxnNThzjnuBmV0Vd1+9hdbLodu5wWhQwMKp/8Sy/Awu2eZUgTQc5HH+bON0bCJJWjidmjjGzwBUJKcWFq5CyRXYVgkb/wEfyQvFelk+xbnN6zjf7xqQOu0rekHG1ChAH+eIl2GbRZpYpmYVukvUrChybPNO+KZ6nlQpsMjm3JjkBV+91iDpExqf5Bq6LGCZFeiTIvpaHnOSYlsKuIrJuEgNFSG1076mh6rCN9XLom5mVRFsHiGFYQjP8+D7PoIggOs4UFWVms2xWq0QRSEMQ0ev2yUeYb1ei5iqanAoNwg2otb3PYp5kHDhRNEWh0OMlAHnM8N2uxd+7tvt9pfKIDHGkKYpGMvAOT9xfMRgwPdUI2kKRkParU2/rpXxfjiInDQ9g9f/BU3Ivnv/Ovnh8qPwiVgXy4UiPon/bvjWoF35WcW/Zhxf44NHx650B/QAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 05 10 38 28&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-05-10-38-28-f71a59bfc24e16a2d9f20422e520fc12-92ba0.png&quot; data-srcset=&quot;/static/2024-11-05-10-38-28-f71a59bfc24e16a2d9f20422e520fc12-cf954.png 200w,
/static/2024-11-05-10-38-28-f71a59bfc24e16a2d9f20422e520fc12-955d7.png 400w,
/static/2024-11-05-10-38-28-f71a59bfc24e16a2d9f20422e520fc12-92ba0.png 636w&quot; data-sizes=&quot;(max-width: 636px) 100vw, 636px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;显然，我们需要明确从服务 B 发出的请求最终会由服务 A 所提供的具体哪一个实例来进行处理，这是负载均衡算法的作用。上图所示的基本架构虽然简单，但围绕该图，我们可以延伸出一系列的扩展性话题，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;负载均衡算法应该位于上图中的哪个位置？是在服务 B 中，还是应该有专门存储这些算法的其他组件？&lt;/li&gt;
&lt;li&gt;服务 A 的某个实例如果出现了问题，服务 B 还能对它进行访问吗？&lt;/li&gt;
&lt;li&gt;服务 A 各个实例的当前负载肯定不一样，服务 B 基于什么原则选择最合适的目标实例呢？&lt;/li&gt;
&lt;li&gt;如何防止对服务 A 实例的访问过程是不均衡的，从而导致服务 A 的某个实例压力太大？&lt;/li&gt;
&lt;/ul&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;Dubbo 包含了哪些负载均衡算法？&lt;/li&gt;
&lt;li&gt;Spring Cloud 内置了哪些负载均衡算法？&lt;/li&gt;
&lt;li&gt;Dubbo 框架在实现负载均衡机制时提供了哪些优化特性？&lt;/li&gt;
&lt;li&gt;一致性哈希算法的实现过程是怎么样的？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到，针对负载均衡算法的提问形式灵活多样。面试官既可以考查一些基本概念，也可以抛出一些发散性问题。同时，针对具体框架中负载均衡算法的实现方式也会经常出现在面试题中。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-6&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;对于上述问题而言，我认为回答的思路还是比较明确的，主要就是对主流负载均衡算法需要有全面的了解。这部分属于理论知识，考查方式比较固定，很难有创新和变化，应对策略上主要以记忆为主。相比到目前为止我们已经介绍的各讲内容，可以说，针对负载均衡算法的回答方式是最直接的。&lt;/p&gt;
&lt;p&gt;然后，无论你现在使用的是哪种分布式服务框架，都需要把它与常见的负载均衡算法结合起来进行掌握。在面试过程中，你可以基于自己擅长的框架来分析具体的算法实现过程。同时，很多时候我们也需要结合上一讲中讨论的话题，把负载均衡算法与远程调用过程结合起来一起讨论。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-8&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在进行具体的工具介绍和源码分析之前，我们首先对负载均衡的类型以及相应的基本策略做简要介绍。&lt;/p&gt;
&lt;h3 id=&quot;负载均衡的类型&quot;&gt;&lt;a href=&quot;#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%9A%84%E7%B1%BB%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;负载均衡的类型&lt;/h3&gt;
&lt;p&gt;负载均衡主要包括服务器端负载均衡和客户器端负载均衡两大类。我们先来看服务器端负载均衡，它的结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-05-10-40-18-5568a7c7247ebdbfb575acbe4b039c26-faa92.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 625px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.919999999999995%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABcElEQVQoz3VSW1OCYBDl//+PmslqmvFWONWLjWOOqXlBUUARJIlrIIxjwmk/0Icyd+Zw+Zbds+csXJqm+AsW/50nSZLlbNvEbFrFZFxE561AuIY0K0Oc1MClaXIoSE6angvXtdHrVjAcPGDQ59F/56GqL1iqLXD5NKdFjCAIPIShj80mRxAwBIiiCLYTIo73YEPv98B2m2L3DXCi2IC2bNLIdSzmDchyA57n0rOCbucGipRLm01LaL3ewjQtfBgrmqiJ5aFOkurQtCadtcGNhlWstEcIoyIlq1DkGnRdo/cBSbiHYz/B0Hl8mjXK8yTXhyAMIRORNC2Tf1dEXKCheBriGVwQhBmr47iwLJvuXiY5jiNMxTbURY8+7BJ7D+NxmxbiYLfbwfdCUhLA90PChoi+yJ449/DUv+OmkXl09OmY830PY6ELYyWTpyY1N6g5w5pt+fxvQ9dfYItiYVlrsqZEvt6R5AuSfAldq2Cu1PADPflV8qoNaxgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 05 10 40 18&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-05-10-40-18-5568a7c7247ebdbfb575acbe4b039c26-faa92.png&quot; data-srcset=&quot;/static/2024-11-05-10-40-18-5568a7c7247ebdbfb575acbe4b039c26-630d2.png 200w,
/static/2024-11-05-10-40-18-5568a7c7247ebdbfb575acbe4b039c26-34069.png 400w,
/static/2024-11-05-10-40-18-5568a7c7247ebdbfb575acbe4b039c26-faa92.png 625w&quot; data-sizes=&quot;(max-width: 625px) 100vw, 625px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，所谓的服务器端负载均衡指的就是在客户端和各个服务实例之间存在一个独立的负载均衡器，所有的请求都将通过这个负载均衡器进行转发并嵌入负载均衡算法。业界关于这种负载均衡器的实现工具也很多，最常见的就是 Nginx。&lt;/p&gt;
&lt;p&gt;我们换一种思路，把上图中负载均衡器所具备的功能放到客户端中，那么就诞生了另一种负载均衡机制，即客户端负载均衡。这时候，负载均衡算法的执行流程发生在客户端本地，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-05-10-40-53-945f2a6244dd0e6b28cd7e3c6e377a07-45fc4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 522px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50.191570881226056%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAB4klEQVQoz5VSaVMaQRTc//8Xkg8pywMlEA8MqAGk5HBxwVW5j12IXEEpWWRnFhA6zVhSlW9mql69mdnZfq+7n7ZarfCZWC6XWC/LqqBRD6NUOIZx44d5G1S5ZV9Av96Hhv9chQcDxUIA5eIhUsktgvmQSW+j141yT0DHcTAcDjAaPeH5efhPfNwJIeC6Ls8j9Pt9NJsWpJTwPMks4HE/nU75RkDLGT/wZxBDvXYK2wozImw/onKtGsK9eYCbbJSPPdJ+U116nqcAZzNP7dd5XVQICa1tnxEwSioB5A0fstc7SF19w21+H3pmB71OhN8SGI9fMZm8IpE4h9U8RaV8hBzp3pl+RbvzeIGsvgfNap7h8XeE4h4oQCO7S6BtBbjWqNeNoFpJQ8gZO5lB168IeM4iJ/y+y+791NBHjBhyRhBao/aTJIqQIgvh6pg4aYxfUpvzyyjOYlE4jruhPJ8vCD7HYvHGWKiQ0lN32v3dIWbe+8+D/i9WD6FePcbTMM57g+CXqFQyFF1uTLEsmx0LZchaO7kxxYXW7bTpWlnNl21X0WrV0GbYPL/PXJHdjTdjUyqZ1O07TQyrGSxyHk0zSGkukc8FPj+HH3Tr9Qe6f0S99xCLfkGSBsZjX9FshJCk5n8B3mzXbNSD5HAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 05 10 40 53&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-05-10-40-53-945f2a6244dd0e6b28cd7e3c6e377a07-45fc4.png&quot; data-srcset=&quot;/static/2024-11-05-10-40-53-945f2a6244dd0e6b28cd7e3c6e377a07-31b3a.png 200w,
/static/2024-11-05-10-40-53-945f2a6244dd0e6b28cd7e3c6e377a07-60bb2.png 400w,
/static/2024-11-05-10-40-53-945f2a6244dd0e6b28cd7e3c6e377a07-45fc4.png 522w&quot; data-sizes=&quot;(max-width: 522px) 100vw, 522px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;客户端负载均衡应用广泛，例如目前主流的微服务架构实现框架 Spring Cloud、Dubbo 等都内置了完整的客户端负载均衡模块。而像老牌的分布式缓存 Memcache 同样也是这一负载均衡策略的典型应用。&lt;/p&gt;
&lt;p&gt;我们来对上述这两种负载均衡机制做一个对比，会发现客户端负载均衡不需要架设专门的服务器组件，负载均衡算法的执行过程被分摊到了每个客户端内部，不会造成明显的单点瓶颈。当然，因为每个客户端自己都需要维护一套服务实例信息，所以需要确保服务实例的变更能够及时通知到各个客户端。&lt;/p&gt;
&lt;h3 id=&quot;负载均衡算法和策略&quot;&gt;&lt;a href=&quot;#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%92%8C%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;负载均衡算法和策略&lt;/h3&gt;
&lt;p&gt;无论使用哪种负载均衡机制，负载均衡算法决定了最终的请求分发效果。常见的负载均衡算法也可以分成两大类，即静态负载均衡算法和动态负载均衡算法。&lt;/p&gt;
&lt;h4 id=&quot;静态负载均衡&quot;&gt;&lt;a href=&quot;#%E9%9D%99%E6%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;静态负载均衡&lt;/h4&gt;
&lt;p&gt;对于静态负载均衡而言，经典的算法包括各种随机（Random）和轮询（Round Robin）算法。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;随机算法&lt;/p&gt;
&lt;p&gt;随机算法是最简单也是最常用的负载均衡算法之一，该算法就是使用一个随机数来决定具体的目标服务实例。&lt;/p&gt;
&lt;p&gt;假设我们持有一个保存所有服务的 serverList 列表，那么只用 JDK 中自带的 Random 工具类就可以实现一个基本的随机算法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1008237450871929300&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`java.util.Random random = new java.util.Random();
int randomPosition = random.nextInt(serverList.size());
return serverList.get(randomPosition);`, `1008237450871929300`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt; random &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; randomPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;randomPosition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随机算法足够简单，但有时候并不能满足我们的需求。例如，如果在集群中存在一些性能有差异的服务器，为了充分利用那些高性能的服务器，可以提升这些服务器的访问权重，这时候就可以引入用加权随机（Weight Random）算法。&lt;/p&gt;
&lt;p&gt;假设存在一个 serverWeightMap 保存着服务器地址与权重之间的对应关系，类似 &lt;code class=&quot;language-text&quot;&gt;(&amp;quot;192.168.10.100&amp;quot;, 1)&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;(&amp;quot;192.168.10.105&amp;quot;, 3)&lt;/code&gt; 这样的结构，那么实现加权随机的一种简单策略就是构建一个新的 serverList 列表，并根据服务权重的数量来添加重复数量的服务提供者地址（这样权重越高的服务被选中的概率就会越大），然后再使用随机算法进行选择，示例代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70454794477903680000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Set&lt;String&gt; keySet = serverWeightMap.keySet();
Iterator&lt;String&gt; iterator = keySet.iterator();
List&lt;String&gt; serverList = new ArrayList&lt;String&gt;();
while (iterator.hasNext()) {
  String server = iterator.next();
  int weight = serverWeightMap.get(server);
  for (int i = 0; i &lt; weight; i++) {
     serverList.add(server);
  }
}

java.util.Random random = new java.util.Random();
int randomPosition = random.nextInt(serverList.size());
return serverList.get(randomPosition);`, `70454794477903680000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; keySet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverWeightMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keySet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Iterator&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; iterator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; keySet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; serverList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iterator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; iterator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverWeightMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; weight&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt; random &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; randomPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;randomPosition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;轮询算法&lt;/p&gt;
&lt;p&gt;所谓轮询，就是一个循环访问所有服务器列表的过程。在循环过程中，如果发现某台服务器可用就把请求分发给它。如果一个循环下来还是没有找到合适的服务器，那么就继续进行新的一轮循环，直到找到目标服务器。&lt;/p&gt;
&lt;p&gt;轮询算法的一种简单的实现方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18261868064807518000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`String server = null;
synchronized(position) {
  if (position &gt; serverList.size()) {
     position = 0;
  }

  server = serverList.get(position);
  position++;
}

return server;`, `18261868064807518000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  position&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;类似加权随机算法，我们也可以实现加权轮循（Weighted Round Robin）算法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;动态负载均衡算法&quot;&gt;&lt;a href=&quot;#%E5%8A%A8%E6%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;动态负载均衡算法&lt;/h4&gt;
&lt;p&gt;对于负载均衡算法而言，权重本质上也是一个可以动态变化的参数，所以也可以基于权重构建动态负载均衡算法。&lt;/p&gt;
&lt;p&gt;典型的动态负载均衡算法实现过程都没有那么简单，常见的包括最少连接数算法、源 IP 哈希算法、服务调用时延算法等。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;最少连接数算法&lt;/p&gt;
&lt;p&gt;所谓最少连接数（Least Connection）算法，就是根据当前服务器的连接数量来决定目标服务器。在系统运行过程中，连接数显然是一个不断在变化的参数，我们可以选择那些连接数较少的服务来接收新的请求。&lt;/p&gt;
&lt;p&gt;因此，当执行分发策略时，我们会根据在某一个特定的时间点下服务实例的最新连接数来判断是否执行客户端请求。而在下一个时间点时，服务实例的连接数一般都会发生相应的变化，对应的请求处理也会做相应的调整。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;源 IP 哈希算法&lt;/p&gt;
&lt;p&gt;在日常开发过程中，有时候我们希望实现这样一种分发效果：来自同一个客户端的请求总是发送到某一个固定的服务器，这时候就可以引入源 IP 哈希（Source IP Hash）算法，该算法会根据请求的 IP 地址来决定目标服务器。只要源 IP 地址不变，那么负载均衡的结果也是固定的。&lt;/p&gt;
&lt;p&gt;源 IP 哈希算法一种实现方案如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;55229560304920970000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`String remoteIp = getRemoteIp();
int hashCode = remoteIp.hashCode();
int serverListSize = serverList.size();
int serverPos = hashCode % serverListSize;
return serverList.get(serverPos);`, `55229560304920970000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; remoteIp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRemoteIp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; hashCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; remoteIp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; serverListSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; serverPos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hashCode &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; serverListSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serverList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serverPos&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务调用时延算法&lt;/p&gt;
&lt;p&gt;服务调用时延（Service Invoke Delay）算法的动态性来自于服务的调用延迟。针对每一台服务器，我们都可以计算一段时间内所有请求的服务调用时延。有了这个参数之后，就可以执行各种计算策略进一步决定选择哪一台服务器来对请求做出响应。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;针对前面介绍的各个负载均衡算法，我们可以通过如下所示的一张思维导图来进行总结：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-05-10-48-23-d2a37837d46bbd01af255d3e5e164a68-a3ca3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 642px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 76.16822429906543%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACq0lEQVQ4y41Ta0/iQBTt//8Tm7jRD2t0xcciQTRZFYVSoKWU8irQ8igFK0JFQODsmYq7atzs3uSmk+nMueeee0Zar9cQ8fr9V/j+AKnbbWTkbcjpl8zIO8hmdnB78xXSarXCarUMDwfBGO12AR2mY6toOxrsVg6ua+Pxccp8hG23UK/JzPS7tOoymg0F0tvqrttBUd9D8voLbpJbm+pbKJVSeHiYwL/3eaYHyzJYsIxWs4RWq0QgI/yKfcmq57mh4vl5geVyGTJotx2yCTAePxBohPl8Fv4T0e06qJQjBIzCNPZQNg9R0PbQsKKoVY8hFfV9Lg5ZuYv7+xHB2lz30eu5YZtvY7F4JvgCLbtBOZrodW12kYAsx6FkzpBOxSGVzRP2H4V/N9xcWoQpGAlthcYihsMe5YihWrkgqwQZ6uh0emQWZ+sx6p5gpzFI1coPLk7heW446SAIOMk7DAYenp6m3HsBdJwy7OYRrFoE3c4xQeMol6vI51PIZC6hKFfIKklImip0iJCBR80m/PoEHOGOuZGNwDNoWhYl4wzFYhxmKY5mU0Wj0aRcUbI9hdOKoV49gSQqO7YJYcPRaAC9cMlDMvURk9R4+ZZs+yGw0E/oOJstKMU67Miq6xySwjs5Ms6+t43jGOHkNHUXCs2qqd/CiXpe51OTi+lb9Z+U7IKAiVCGjbFXmwNz9PsuPJFen2t6rl6jBD7lGHPqE7YfYDoNyHROeYYsesZBXcIoCtArMvyPpyfaU9U0/bbPixHkc99QKJzTzDY1vaaWmTDN0s0L4CvWer3+kKvfhnbdBtkewNB32eIBKpVzGIYJXT/isCJhFtTvfxjiA8O3wCJGI58ssgTLkVmWD6BCH3ZpmyTfco5tZ2Gayt8BP4J/FkEwCY3dtmP0ZiJ8fr8AFfhTMIFGKjoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 05 10 48 23&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-05-10-48-23-d2a37837d46bbd01af255d3e5e164a68-a3ca3.png&quot; data-srcset=&quot;/static/2024-11-05-10-48-23-d2a37837d46bbd01af255d3e5e164a68-f8a33.png 200w,
/static/2024-11-05-10-48-23-d2a37837d46bbd01af255d3e5e164a68-8ae0d.png 400w,
/static/2024-11-05-10-48-23-d2a37837d46bbd01af255d3e5e164a68-a3ca3.png 642w&quot; data-sizes=&quot;(max-width: 642px) 100vw, 642px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;源码解析-6&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;有了理论知识，我们接下来讨论负载均衡的实际应用。诸如最少连接数算法、服务调用时延算法等动态负载均衡算法在设计和实现上都比较复杂，我们重点来看一下主流开源框架中对它们的实现机制。在接下来的内容，我们以 Dubbo 框架为例展开讨论。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-负载均衡整体结构&quot;&gt;&lt;a href=&quot;#dubbo-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 负载均衡整体结构&lt;/h3&gt;
&lt;p&gt;在 Dubbo 中，专门提供了一个 LoadBalance 接口来提供负载均衡能力，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30860363659054180000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
    @Adaptive(&amp;quot;loadbalance&amp;quot;)
    &lt;T&gt; Invoker&lt;T&gt; select(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation) throws RpcException;
}`, `30860363659054180000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RandomLoadBalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalance&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;loadbalance&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到 LoadBalance 接口只有一个方法，即在一批 Invoker 列表中选择其中一个 Invoker 进行返回。这里，我们可以从该接口上的 &lt;code class=&quot;language-text&quot;&gt;@SPI(RandomLoadBalance.NAME)&lt;/code&gt; 注解中看到 Dubbo 默认加载的是 RandomLoadBalance 类，即随机负载均衡。除了 RandomLoadBalance 类之外，Dubbo 还提供了其他多种负载均衡策略，整体的类层结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-05-10-49-27-62c59b2b1f5646dd706f22c3812aec23-ba470.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 649px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 59.78428351309707%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAAB3klEQVQoz51SbU/aUBjt//+2ZF80823ZPsxFolGic+NFqiAvDtio7aWIFoggtOCkWKAvZ8+9TdlcmizxJk/ubc+5J+fc55EQs4IgELtp9tG+OUWlvAedJdHvM4RQsOL8u6R4QV/s/XsD3c4RqlcfSXiPzvXXCobkmT3Fw6CFXk8lcYbJZLTCXyc4m8Ky2iKqZbbw9GT+XzACX1YYeTAwUK9+wHHyDYqFd+gYNfh/RY6rWIfRsswhmJbCzx/HuFZO0Os2XySIdeg4NpZLB8vFMxaiZvA8V4COMxPYeDwC53Hc9z0hGH3ziu5yrpRJb+BC3kIhv4NcdhOpr2/BWJne7R7p1Bou8+8FdpbdgJxbJ8cVjEYPyKTXcC5v4uJ8m/YtZDPrKBU/QdJUGXe3l7htF+iNSmiyLIZDgxpiQ9POKGpKYHpTpuhfaDZ7wjljOeIXYdDd8H6eRqvEmwIxWz71Yb7wyLpP1j24ro/HR5tGZSpw/s+yfsG2nwnjPKqlj/mccwNEOlL0wK67QKPxDbVqUjip104oagKamiaRKbm+E82plA9xVTmC0jgVZ87vdpRo4P4I8hjV77u40Q/Qah5QRCq2D1VJYDK20O0qUK8/E34YYlRM26cnSkDXC6vu/wZDLYMHMErN6wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 05 10 49 27&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-05-10-49-27-62c59b2b1f5646dd706f22c3812aec23-ba470.png&quot; data-srcset=&quot;/static/2024-11-05-10-49-27-62c59b2b1f5646dd706f22c3812aec23-47540.png 200w,
/static/2024-11-05-10-49-27-62c59b2b1f5646dd706f22c3812aec23-ee316.png 400w,
/static/2024-11-05-10-49-27-62c59b2b1f5646dd706f22c3812aec23-ba470.png 649w&quot; data-sizes=&quot;(max-width: 649px) 100vw, 649px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从上图中，我们看到存在一个 AbstractLoadBalance 抽象类，它实现了 LoadBalance 的 select 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;46235087989619420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Invoker&lt;T&gt; select(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation) {
   if (invokers == null || invokers.size() == 0)
      return null;
   if (invokers.size() == 1)
      return invokers.get(0);
   return doSelect(invokers, url, invocation);
}`, `46235087989619420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invokers &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，从设计模式上讲，这里采用的是经典的模板方法。通过模板方法，具体负载均衡算法由 AbstractLoadBalance 子类中的 doSelect 方法进行实现。&lt;/p&gt;
&lt;p&gt;同时，我们在 AbstractLoadBalance 中还看到了如下所示的 getWeight 方法。从方法命名上看，该方法用来计算权重，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;75295549208287850000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected int getWeight(Invoker&lt;?&gt; invoker, Invocation invocation) {
   // 从 URL 中获取权重
   int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
   if (weight &gt; 0) {
      long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
      if (timestamp &gt; 0L) {
         int uptime = (int) (System.currentTimeMillis() - timestamp);
         // 从 URL 中获取预热时间
         int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
         if (uptime &gt; 0 &amp;&amp; uptime &lt; warmup) {
            // 计算预热权重
            weight = calculateWarmupWeight(uptime, warmup, weight);
         }
      }
   }
   return weight;
}`, `75295549208287850000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getWeight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 从 URL 中获取权重&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WEIGHT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEFAULT_WEIGHT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REMOTE_TIMESTAMP_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; uptime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 从 URL 中获取预热时间&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; warmup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WARMUP_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEFAULT_WARMUP&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uptime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; uptime &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; warmup&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 计算预热权重&lt;/span&gt;
            weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateWarmupWeight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uptime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; warmup&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; weight&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到代表权重的 weight 参数是从 URL 中传入的。而基于上述代码，我们发现这里的处理逻辑显然并没有那么简单，而是用到了所谓的预热（Warmup）机制。我们看到 Dubbo 首先会获取服务启动时间，然后再与预热时间进行比较。如果启动时间小于预热时间，则会调用 calculateWarmupWeight 方法来重新计算预热权重。&lt;/p&gt;
&lt;p&gt;从代码逻辑上看，预热权重最小为 1，并在预热时间内随启动时间逐渐增加。这样设计的原因在于：JVM 从启动成功到处于最佳状态需要一段时间，在这段时间内虽然服务可以接收请求，但显然不应该接收过多请求。所以，Dubbo 通过预热机制确保在预热时间内该服务受到一定的保护，直到其处于最佳运行状态。&lt;/p&gt;
&lt;p&gt;预热机制在 Dubbo 的多个负载均衡算法中都得到了应用，是一种实现上的技巧，为我们设计类似的应用场景提供了一定的参考价值。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-负载均衡算法实现示例&quot;&gt;&lt;a href=&quot;#dubbo-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0%E7%A4%BA%E4%BE%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 负载均衡算法实现示例&lt;/h3&gt;
&lt;p&gt;接下来，就让我们看看 Dubbo 中使用预热机制的场景和方式。我们重点介绍 LeastActiveLoadBalance 类，这是一种典型的动态负载均衡算法。&lt;/p&gt;
&lt;p&gt;LeastActiveLoadBalance 继承自 AbstractLoadBalance 类，并实现了如下所示的 doSelect 方法。该方法比较长，我们对代码进行了部分裁剪。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;95808682379124080000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
protected &lt;T&gt; Invoker&lt;T&gt; doSelect(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation) {
   // 获取所有的 invoker 并执行计算
   for (int i = 0; i &lt; length; i++) {
      Invoker&lt;T&gt; invoker = invokers.get(i);
      // 通过 RpcStatus 获取当前这个 invoker 并发数
      int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
      // 通过预热机制计算权重值
      int afterWarmup = getWeight(invoker, invocation);

      // 发现最小的活跃数，重新开始计算
      if (leastActive == -1 || active &lt; leastActive) {
         // 记录 leastActive 为当前的活跃数，并重置最小计数，基于当前最小计数重新计数
         // …
      } else if (active == leastActive) {
         // 当前 invoker 的活跃数与最小活跃数相等,则记录权重
         // …
      }
   }

   // 如果我们恰好有一个调用程序具有最少的活动值，那么直接返回这个调用程序
   if (leastCount == 1) {
      return invokers.get(leastIndexs[0]);
   }

   // 如果每个 invoker 有不同的权重
   if (!sameWeight &amp;&amp; totalWeight &gt; 0) {
      // 在总权重范围内随机一个值
      int offsetWeight = random.nextInt(totalWeight) + 1;
      for (int i = 0; i &lt; leastCount; i++) {
            // 获取 i 位置的那个最小活跃在 invokers 里面的位置信息
            int leastIndex = leastIndexs[i];
            offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
            if (offsetWeight &lt;= 0) {
               // 返回这个位置
               return invokers.get(leastIndex);
            }

      }
   }

   // 具有相同权重或者是总权重 = 0 的话就均匀返回
   return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}`, `95808682379124080000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取所有的 invoker 并执行计算&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 通过 RpcStatus 获取当前这个 invoker 并发数&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; active &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getActive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 通过预热机制计算权重值&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; afterWarmup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getWeight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 发现最小的活跃数，重新开始计算&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastActive &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; active &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; leastActive&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 记录 leastActive 为当前的活跃数，并重置最小计数，基于当前最小计数重新计数&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;active &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; leastActive&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 当前 invoker 的活跃数与最小活跃数相等,则记录权重&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果我们恰好有一个调用程序具有最少的活动值，那么直接返回这个调用程序&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastCount &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastIndexs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果每个 invoker 有不同的权重&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sameWeight &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; totalWeight &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 在总权重范围内随机一个值&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; offsetWeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;totalWeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; leastCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 获取 i 位置的那个最小活跃在 invokers 里面的位置信息&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; leastIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; leastIndexs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            offsetWeight &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getWeight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offsetWeight &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 返回这个位置&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 具有相同权重或者是总权重 = 0 的话就均匀返回&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastIndexs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leastCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上述代码中，我们对关键流程添加了注释。该算法首先会对所有的 Invoker 进行轮询，找出所有活跃数最小的集合。如果这个集合的数量只有 1，那么就可以直接返回当前的 Invoker。如果集合中所有 Invoker 的权重相同，那么随机返回一个。而如果这些条件都不满足，那么就获取一个具有最小活跃数的 Invoker。&lt;/p&gt;
&lt;p&gt;为了实现扩展性，Dubbo 提供了 SPI 机制，允许开发人员自定义负载均衡算法，我们会在后面介绍微内核架构和 SPI 机制时给出一个简单的案例。你也可以根据自己的需要尝试实现新的负载均衡算法。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-8&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;在分布式系统面试中，负载均衡是一道高频面试题。针对常见的负载均衡算法以及它们的特性，面试官考查的是候选人对分布式系统基本概念的理解。因为负载均衡的各种算法以及特性相对固定，所以这类题的考点是非常明确的。&lt;/p&gt;
&lt;p&gt;在回答这类面试题时，需要把握两点。首先，我们要介绍负载均衡算法的分类，即静态负载均衡和动态负载均衡。其次，我们需要列举常见负载均衡算法，并基于自己的理解分析这些算法的功能特性。针对随机、轮询等静态负载均衡算法，我们的回答思路是给出基本的设计策略和所能达到的效果，以及如何将这些静态负载均衡算法转换为动态负载均衡算法的实现方法。因为静态负载均衡算法相对都比较简单，所以这部分内容不是回答的重点。我们需要详细介绍的是一致性哈希、最少连接数等动态负载均衡的实现原理。&lt;/p&gt;
&lt;p&gt;另一方面，关于负载均衡算法的考查，基于具体开源框架内部的实现细节进行提问也是另一种比较常见的方式。例如，如果被问到类似 &lt;code class=&quot;language-text&quot;&gt;Dubbo 框架在实现负载均衡机制时提供了哪些优化特性？&lt;/code&gt; 这样的问题，回答起来是有难度的，因为它考查的并不仅仅是对 Dubbo 中所提供的负载均衡算法的描述，而更多的是问到了框架的底层设计思想和实现原理，需要面试者对 Dubbo 的源码有较深程度的理解，并能抓住细节。在回答上，就需要提到 Dubbo 中的“预热”机制，该机制的目的是确保服务在刚启动的一段时间内得到保护，避免因为负载均衡导致出现不可用的情况。在回答这道题时，这些内容背后的设计思想都应该被介绍到。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-6&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲内容梳理了分布式系统开发过程中所采用的负载均衡算法。在 Spring Cloud 中所实现的核心负载均衡算法包括随机、轮询、加权响应时间、并发量最小优先等，而 Dubbo 中则提供了随机、轮询、最少活跃调用数和一致性哈希等算法。可以看到，这两个框架在实现主流算法的同时，也根据框架自身的特点提供了一些比较有特色的策略。当然，这两个框架也都内置了扩展入口供开发人员实现自定义的负载均衡算法。&lt;/p&gt;
&lt;p&gt;在介绍完负载均衡之后，我们接下来要进入到“服务容错”这一技术组件的讨论。服务容错的实现方式有很多，其中集群的构建为容错机制提供了天然的基础。那么，什么是集群容错？现实中又有哪些常见的集群容错策略呢？下一讲将对这些问题展开详细的分析。&lt;/p&gt;
&lt;h1 id=&quot;服务容错：什么是集群容错？有哪些集群容错策略？&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E5%AE%B9%E9%94%99%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%EF%BC%9F%E6%9C%89%E5%93%AA%E4%BA%9B%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E7%AD%96%E7%95%A5%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务容错：什么是集群容错？有哪些集群容错策略？&lt;/h1&gt;
&lt;p&gt;在前面两讲内容中，我们对远程过程调用中的负载均衡机制进行了详细的讨论。而在介绍负载均衡的实现策略时，我们也提到了集群这一概念。集群的构建一方面能够为实现负载均衡提供基础，另一方面，它也能够有效应对服务访问出错的场景，这就是集群容错。&lt;/p&gt;
&lt;p&gt;在分布式系统运行过程中，远程调用发生失败的现象不可避免。为了应对服务访问失败，集群容错是一种简单高效的技术组件。&lt;/p&gt;
&lt;p&gt;那么，什么是集群容错？常见的又有哪些集群容错策略呢？这一话题也是分布式服务相关的一道高频面试题，本讲内容将对这一主题展开全面的讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-6&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，对于服务提供者和消费者而言实际上存在一种依赖关系。一方面，每个服务自身可能会发生异常情况，更为重要的，这种依赖关系会导致系统中的其他服务也发生调用失败，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-05-17-df0dbd0dd42f326b8319da58761955e2-daebf.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 502px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.23505976095617%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABY0lEQVQoz42S6U7CUBCF+/7vocHI4oYiS6CCGCqtgNCCLC2lK6UsZSnEwHFoIjHBBCaZHzOZOfPl3MtwpRt02k8ov1/jQ4hC4CNUP6L+mYTnLbCP3W53djKNehyz6SuqlTBa0j3qtRjmXhGKwsJ1JwfBc4NxHAeqqsI0DYiiCF3Xgnrf/4/uFDHzq2xZFnRDhyzL8H0/6G232yOCU7SM728wGk2IyAXPV6BpJoZDF6vV+iCwF/5LuF5vMB7PMJ8vA58nE4/smQY7TLWSRF/J0oPc4a14BU3NQxu8oCmx2Gy+adANrOh2u5QdWvLRbBYg9zLk+wNq1Tjts9C1PMRGFkyvl6CbVbSaMVhmGst5kWoJipyiAyUimJG/JmzbDsRluY+BmqOZCqRGBO2vW3izQrBjGiwYgY9h5OToUhhiPQqOu4Shp4jwmUScI48WixUkMYWhnaGvFoJQDoErXZBYmvoJ/ADUzFSuodErFAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 05 17&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-05-17-df0dbd0dd42f326b8319da58761955e2-daebf.png&quot; data-srcset=&quot;/static/2024-11-06-11-05-17-df0dbd0dd42f326b8319da58761955e2-d8b35.png 200w,
/static/2024-11-06-11-05-17-df0dbd0dd42f326b8319da58761955e2-df7a1.png 400w,
/static/2024-11-06-11-05-17-df0dbd0dd42f326b8319da58761955e2-daebf.png 502w&quot; data-sizes=&quot;(max-width: 502px) 100vw, 502px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;那么问题就来了，一旦出现上图中的失败场景，我们有什么应对策略呢？这是开发任何一个分布式系统必须要考虑的问题，也是面试官非常喜欢考查的一个问题。&lt;/p&gt;
&lt;p&gt;就技术体系而言，我们可以把应对远程调用失败场景的各种手段和方法统称为服务容错（Fault Tolerance），而集群容错是服务容错的其中一种实现方式。我们知道，所谓集群，就是同时存在一个服务的多个实例。一旦我们访问其中一个实例出现问题，原则上可以访问其他实例来获取结果。&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;当然，和负载均衡一样，主流的分布式服务框架也都内置了集群容错机制。例如 Dubbo 框架就包含一组非常常用的集群容错实现策略。在面试过程中，针对具体框架中集群容错的底层实现原理进行讨论的场景并不少见。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-7&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;对于集群容错，我们首先还是有必要分析远程调用发生依赖失败的影响，或者说我们需要引入集群容错机制的原因，这里就引出一个非常适合作为面试话题来展开的概念，即 &lt;code class=&quot;language-text&quot;&gt;雪崩效应（Avalanche Effect）&lt;/code&gt;。雪崩效应是我们引入容错思想和模式的根本需求。以我的面试经历而言，雪崩效应在分布式系统相关面试场景中出现的频次比较高，在介绍具体容错机制之前，先来对这个概念进行展开有助于提升候选人给到面试官的第一印象。&lt;/p&gt;
&lt;p&gt;回答这一问题的第二个要点是对集群容错的各种实现策略的详细分析。这部分内容属于理论知识体系，需要进行记忆。幸好常见的集群容错实现策略并不是很多，因此掌握起来并不困难。&lt;/p&gt;
&lt;p&gt;最后，我们还是需要理论联系实际。集群容错的几种代表性实现策略在 Dubbo 等主流的开源框架都有体现。在面对面试官时，基于具体的框架底层实现原理来展示自己对集群容错机制的掌握程度无疑是一种加分项。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-9&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;正如前面提到的，服务依赖失败是我们在设计分布式系统时所需要重点考虑的服务可用性问题，因为服务依赖失败会造成失败扩散，从而形成服务访问的雪崩效应。让我们先从这个过程开始讲起。&lt;/p&gt;
&lt;h3 id=&quot;雪崩效应&quot;&gt;&lt;a href=&quot;#%E9%9B%AA%E5%B4%A9%E6%95%88%E5%BA%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;雪崩效应&lt;/h3&gt;
&lt;p&gt;下图展示了雪崩效应的示意图，可以看到 A、B、C、D、E 这 5 个服务存在依赖关系，服务 A 为服务提供者，服务 B 为服务 A 的消费者，服务 C、D 和 E 则是服务 B 的消费者。现在，假如服务 A 变成不可用，就会引起服务 B 的不可用，并将这种不可用性逐渐扩散到服务 C、D 和 E 时, 从而造成了整个服务体系发生雪崩。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-08-28-e7c9b4f01d8d83685a9e97d7a659af46-d80b6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 492px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.016260162601625%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABgUlEQVQoz41SiWrCQBDN/39ESw8oiKigxTNqadW2HtFEk3iVGo/ihdpWE03M62TVgGLBgWFn3868mX27HE7Mtm22mrSKiQTUQACC1wvR74NCXkkld3nbLcs9de4/wrVloRaPo/sYQub6Cvn7e2ihICRqciA8Z9w58EC6Wq2wXP5CVRU0mw0W67ru5lw04dGUa5NIdIxGEwyHYxZvNtblhAeQaWhuUVez6GppKHKUudZJQ1Ve97nbo+bulc912RHaVJxEvxdB4f0BpaIHvW4Y1WrSJbz4yo4ZhgFJFIlURrFYQKUsQKFYlmuujmcfxbJs0sYggg0lbjCf/2A8nmKxmONwGwebzb73kzn7GSaTKcMNw2R1DocjEydWeHQ+nyDXYmi30qQb6aXkWbFKGrZbPE0apvMoPtpJ0jLHzlpNifAQvX6KMPpe2gvKQsQhDKLfjSHzfIOq5Mdsmkaj/kYdTdTrKUoMI5u5hVDy4GsQgyTxO8KWwPZlwnPZOwz6PMniwx8eUvAnm4YREAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 08 28&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-08-28-e7c9b4f01d8d83685a9e97d7a659af46-d80b6.png&quot; data-srcset=&quot;/static/2024-11-06-11-08-28-e7c9b4f01d8d83685a9e97d7a659af46-3e95a.png 200w,
/static/2024-11-06-11-08-28-e7c9b4f01d8d83685a9e97d7a659af46-11c87.png 400w,
/static/2024-11-06-11-08-28-e7c9b4f01d8d83685a9e97d7a659af46-d80b6.png 492w&quot; data-sizes=&quot;(max-width: 492px) 100vw, 492px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;服务雪崩的产生是一种扩散效应，我们可以对上图中的现象进行剥离，先从服务 A 和服务 B 这两个服务之间的交互进行切入。下图展示了雪崩效应产生的三个阶段，即首先服务提供者 A 发生不可用，然后服务消费者 B 不断进行重试加大了访问流量，最后导致服务 B 自身也不可用。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-09-11-8871c8a88a751876bbf16c18a2e9d0fe-19a4b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 435px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 68.50574712643677%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACfUlEQVQ4y4VTa0/iQBTt//8L+2GNikpWEFATUVytRKnyqAFaSqGlpYAUpBTKq3Xx7J0msrqb1UlupvM4Z+6555aTpVMUCwmIpSQq5RM8ikmIooDlysdqtaRYbcL3V1gslsjdHaNKdxmuVEwSJhXiyuUiuJfgDkLuOxHuwtBTWMxvcHi4hV7PhuOMMBr9ifHYof0+Hu4PMJ/xuM1+I9JtKPIPLBdZnKcT4BpqCTU5j0pFQEMVodQKmEwcfDY0TUa9XoRKWLVeQqMhoka44dAGxy68vgJB8GsD8LwZHQ4pozFl6WA6ndIjE7iuG86fjZBw/bqGH/ibTcMwIMsyWq0WdF2HbdsYDAbodDrodrsshf8TMnBNknB9dYWOZZEcDV8Nlr2ua/SABctqw2qb4cNBEICbZm8gH8WhJOLoXpxjeJnBZSqFwfPzRvLb7LpjynaIQj4BZ5Sl2ifRbJzCaBFuyJP7P8HNeB75nR2IkQi0JF3MZHAWi8Glus1nM8zexWIxJ2IX+YcYxg4P4W6bviOkkD3AI5u9AKcqCkxNh0FSW80mmqr6peTBwCZnFSqPCkWRYJo6rethr3J/X16v13TYCIPVxTTN0CRmTK/XC/eZ22/d8RK8/OvyB0KK3tMTFdsKXe33+yEZa2xmBnN5SZmwsSITplSKD4SaLKGt1sMw6ySD1oHvfyq5S1m3lBqkYgF8Og2LKapWMaUe5ZbXV6juUWEP9tFOJeGep3FEJtmUjfvOZRZMap/6USADvcwFypFdyITT4jFMqEMuj1PgOtQmuf09PESjkGjDJEKBenJCLnsU7C/xPG8zj4m0cHIMI32GWyK+jx7gkQgZThRy+A2e8AEG5lRZ5gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 09 11&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-09-11-8871c8a88a751876bbf16c18a2e9d0fe-19a4b.png&quot; data-srcset=&quot;/static/2024-11-06-11-09-11-8871c8a88a751876bbf16c18a2e9d0fe-5fb9b.png 200w,
/static/2024-11-06-11-09-11-8871c8a88a751876bbf16c18a2e9d0fe-56ca1.png 400w,
/static/2024-11-06-11-09-11-8871c8a88a751876bbf16c18a2e9d0fe-19a4b.png 435w&quot; data-sizes=&quot;(max-width: 435px) 100vw, 435px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，通过用户不断提交服务请求或代码逻辑自动重试等手段，服务 B 会进一步加大对服务 A 的访问流量。因为服务 B 使用同步调用，会产生大量的等待线程占用系统资源。一旦线程资源被耗尽，服务 B 本身也将处于不可用状态。这一过程在整个服务访问链路上进行扩散，就形成了雪崩效应。&lt;/p&gt;
&lt;p&gt;显然，应对雪崩效应的切入点不在于服务提供者，而在于服务消费者。我们不能保证所有服务提供者都不会失败，但是我们要想办法确保服务消费者不受已失败的服务提供者的影响，或者说需要将服务消费者所受到的这种影响降到最低，这就是服务容错的本质需求。而集群容错可以很好地应对这一需求。&lt;/p&gt;
&lt;h3 id=&quot;集群容错的策略&quot;&gt;&lt;a href=&quot;#%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E7%9A%84%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;集群容错的策略&lt;/h3&gt;
&lt;p&gt;在上一讲中，我们已经介绍了集群和客户端负载均衡，从服务容错的角度讲，负载均衡不失为是一种可行的容错策略。而我们今天要介绍的集群容错则是在负载均衡的基础上添加了各种容错策略，包括常见的 Failover（失效转移）、Failback（失败通知）、Failsafe（失败安全）和 Failfast（快速失败） 以及不大常见的 Forking（分支）和 Broadcast（广播） 等。我们一一来看一下。&lt;/p&gt;
&lt;p&gt;Failover 是最常见、最实用的集群容错策略。Failover 即失效转移，当发生服务调用异常时，重新在集群中查找下一个可用的服务实例。为了防止无限重试，通常对失败重试最大次数进行限制，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-11-24-4d624260adf74e3a893e5c6a74d4c84d-1093f.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 553px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 40.86799276672694%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABhklEQVQoz31SaU/CQBTs//8LGhP1CzHEIoYjJoiaQOWoUKAUy1GwtEBLGwimhUYY3y4ag9dLJt036c7bnVlht9vhEFv+3W63WCwWWC6X8H0PrLy5g2IhBqWWgFy9RO1JxJMsolKOE8fWKQj4p1zXhW3bMAyD981mHc+dOFqNGMqPp5Ar5xzFwgnM0TWGxg0E3/cxndrwPOcDLu9XqxU/ZRRFCIKA94pSh6rK6HYVdHWFBpSgaTJ6vQb05xr6/TaERymO2fQWSp1NFqGpSbyMsqhWcyQIhGHIhRm+VxS9/eAEqRiD6+Rwnz9G/vYIUuEMtpWFJGXw+hrSps2vYqzYMOb3gaDjTGBZQ8xmFhxnj/F4yIMIghDMkvl8TlZ40HWd/BxgNDJoj4mHhzvIcgWTiUV8nw5i/R3Ker2mHyckPuZgoqVSEa2miEE/Q56l0FavoLWTxCWIS6PXzUHYP5MvfF6PhcASNk2Tp7yhcJwZJT5IUZppqK1LdDSRwkmS/xewzCzMlzzeAQL1TObK/JgHAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 11 24&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-11-24-4d624260adf74e3a893e5c6a74d4c84d-1093f.png&quot; data-srcset=&quot;/static/2024-11-06-11-11-24-4d624260adf74e3a893e5c6a74d4c84d-7baba.png 200w,
/static/2024-11-06-11-11-24-4d624260adf74e3a893e5c6a74d4c84d-2a76e.png 400w,
/static/2024-11-06-11-11-24-4d624260adf74e3a893e5c6a74d4c84d-1093f.png 553w&quot; data-sizes=&quot;(max-width: 553px) 100vw, 553px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;相较 Failover，Failback 则采用了不同的实现方式，它会记录每一次失败的请求，然后再基于一定的策略执行重试操作。显然，这种容错策略适合于那种时效性不高的操作，常见的包括发送短信等消息通知类业务。&lt;/p&gt;
&lt;p&gt;Failsafe 的意思是失败安全，该策略并不会对所发生的异常做直接的干预，而是将它们记录下来，确保后续可以根据日志记录找到引起异常的原因并解决。&lt;/p&gt;
&lt;p&gt;还有一种比较容易混淆的策略称为 Failfast，该策略在获取服务调用异常时立即报错。显然，Failfast 已经彻底放弃了重试机制，等同于没有容错，一般用于非幂等性的写入操作。另一方面，在特定场景中可以使用该策略确保非核心业务服务只调用一次，为重要的核心服务节约宝贵时间。&lt;/p&gt;
&lt;p&gt;以上三种集群容错策略之间的区别可以参考下图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-12-01-9453c179c4b6c3b7950c6b8634532e47-53b00.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 608px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.848684210526315%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABvklEQVQoz3VSa0/bQBD0//8VFVQUJPoFpAbafqnUljSEgIgTgrHxA2L7msZx4lf8OE/3zqljVe1Iez6tblYzs1ZAWCx+YaJe4mb4DqPhMabTHpIkBuc1JpOPuLs9wfXgCLOHHsJwJSjQ9Wuo96fUP8b4/j08z5J9Jc9zBMEKi58Gno0hzOcbMF9DURSo6xqM6bCsEQx9SHcN2yyTxNXKgzr+QkK+wZ2riKKwGcgYQ7Z7VHGQKnRQNyd9yrLG3wjDCGQCeV61PcV1XbKXSHJZFnAcG5r2iPV6LR9wXslizG/7cRzjxXGw3TZCiiKn2BYUg94MTNN0R94rEXa76PZFlWVFQra7gVy6qKoKirC7XAbYbBjm8xleXx9IhU+EUhLD0Jd9x5lS35PZCkRRQIvQKF+VODOy3ahVxGGaFm3yDD/6B7S1t5hOPsgY5JbVC/Sv3mDQP4T22KNlBLstD3A3OsL3rwdyy65r7gcKiDyyLKYtJnRPW8vChojEsW1SG7YRiNzSNCZOIku8kwP/ZPIvCIXBck62xlDVK/ieIaP4H8QcpRt0twTyvMCT9gmWeQ7TOIdtf5b/bMPh7VvO9/ffgUT25m7UxKEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 12 01&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-12-01-9453c179c4b6c3b7950c6b8634532e47-53b00.png&quot; data-srcset=&quot;/static/2024-11-06-11-12-01-9453c179c4b6c3b7950c6b8634532e47-17576.png 200w,
/static/2024-11-06-11-12-01-9453c179c4b6c3b7950c6b8634532e47-52273.png 400w,
/static/2024-11-06-11-12-01-9453c179c4b6c3b7950c6b8634532e47-53b00.png 608w&quot; data-sizes=&quot;(max-width: 608px) 100vw, 608px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;除了这些常见的集群容错机制之外，在一些分布式服务框架中，还实现了一些特殊的策略，例如提供分支调用机制的 Forking 策略和提供广播机制的 Broadcast 策略。这些策略的使用场景比较少，这里不做具体展开。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-7&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;dubbo-中的集群&quot;&gt;&lt;a href=&quot;#dubbo-%E4%B8%AD%E7%9A%84%E9%9B%86%E7%BE%A4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 中的集群&lt;/h3&gt;
&lt;p&gt;服务容错的实现方法和策略有很多，我们接下来重点讨论 Dubbo 中主要采用的集群容错实现策略和底层原理。&lt;/p&gt;
&lt;p&gt;Dubbo 中的整个集群结构如下图所示。这张图比较复杂，涉及到 Dubbo 中关于集群管理和服务调用的诸多概念。为了讨论集群容错，我们必须首先理解这种图中的相关概念，进而把握 Dubbo 对集群的抽象。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-12-46-fecfd0f98972553e0a67a7bf23252835-69f72.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 464px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 72.19827586206897%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAClklEQVQ4y4VTi1LaQBTN//+F1To4WsW32LEqEN4BDJF3AgkPAwRMeBjAwulJWi1tx+nO3CS7e/fcc8+eCMnEOeq1SxTkEzzkg4xjVMuXeFSO8fiYxNtYrVb+u1aVUS6dQSmcIp8LQn448c+q9RDC9wEISuGKaQ9oNi6RSe1CSgfg2CLGThilUgwezmw2fQfO50WMhrdoaMxnbjazB0X+grmb4nwPQr1eQzabZlUF1WqJIAqaTZWsy3h5mcFxHKiqCtd1Yds2KpUKmUl4emqj3dbJrIpO24BhNLjWgdDvWwSpod8fYji0MRqN39ksl6+cj9Dtdv1YLpf++mhks9AEnY4JWVZYaALLemYnLltWYmRzj2oljETsCFLmjPMIWVxAkkSydMmqzFbzPDREOh0hozu0W3fU+ZQaBqE3v8EaxJFMHECQMoesKXHxlAn71HAbzvMdXpcxpFNXmExeMJ1OsVgs+D3D9XUQPfMGLSOEuLiNaHgLpWKQmouIRj5BaDZrKJYklMt5FItZtp8jC7JN3Pr6OM7Y16/X63GvRK10ymNiMDApUZ9tG2Te4/4T3wMI+GCMx1MeGJHVhNrMCGRA0zRGgwUWfo7HuNHQf9kKWK8BYc3nz1htxNpPms/nZNMn+O+Lisdvkc8eolK+Qjq5j0Q8QE+e0Z8hRCJ7m4Drd6DNb294GlqW5TNQVRlG88L3rBjd4kV8JugOfXmOVHL335b/Bvfa9cBarZZvHdM0CVrjXIeua6jRr54HNa1OPVsfa/gG6LHzwHRd92/adeewnSnttPR1syz7j3MfAm6Cbo5uV0cuF6JFDunBr4iJR9QvSk3D9PDN/xm+tb9afffnBTnG/1bEZCyiqBwgK+1CU0+8v5wa7uAHNVkFxcmqg0wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 12 46&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-12-46-fecfd0f98972553e0a67a7bf23252835-69f72.png&quot; data-srcset=&quot;/static/2024-11-06-11-12-46-fecfd0f98972553e0a67a7bf23252835-c0897.png 200w,
/static/2024-11-06-11-12-46-fecfd0f98972553e0a67a7bf23252835-d12a6.png 400w,
/static/2024-11-06-11-12-46-fecfd0f98972553e0a67a7bf23252835-69f72.png 464w&quot; data-sizes=&quot;(max-width: 464px) 100vw, 464px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图展现了 Dubbo 中的几个重要技术组件，我们一一来展开。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invoker：在 Dubbo 中，Invoker 是一个核心概念，代表的就是一个具体的可执行对象。&lt;/li&gt;
&lt;li&gt;Directory：即目录，代表一个集合，内部包含了一组 Invoker 对象。&lt;/li&gt;
&lt;li&gt;Router：即路由器，根据路由规则在一组 Invoker 中选出符合规则的一部分 Invoker。&lt;/li&gt;
&lt;li&gt;LoadBalance：即负载均衡，对经过 Router 过滤之后的一部分 Invoker 执行各种负载均衡算法，从而确定一个具体的 Invoker。&lt;/li&gt;
&lt;li&gt;Cluster：即集群，从 Directory 中获取一组 Invoker，并对外伪装成一个 Invoker。这样，我们在使用 Cluster 时就像是在使用一个 Invoker 一样，而在这背后则隐藏了容错机制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于上述分析，今天内容所要介绍的重点是 Cluster。我们首先来看看 Dubbo 中 Cluster 接口的定义，该接口只包含一个 join 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18538510467184243000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(FailoverCluster.NAME)
public interface Cluster {
    @Adaptive
    &lt;T&gt; Invoker&lt;T&gt; join(Directory&lt;T&gt; directory) throws RpcException;
}`, `18538510467184243000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FailoverCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; directory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cluster 接口中包含另一个与集群相关的重要概念，即前面提到的 Directory。Directory 本质上代表多个 Invoker，我们需要知道可以通过它获取一个有效 Invoker 的列表。&lt;/p&gt;
&lt;p&gt;换一个角度，Dubbo 中的 Cluster 也相当于是一种代理对象，它在 Directory 的基础上向开发人员暴露一个具体的 Invoker，而在暴露这个 Invoker 的过程中，万一发生了异常情况，Cluster 就会自动嵌入集群容错机制。那么，Cluster 是如何做到这一点的呢？&lt;/p&gt;
&lt;p&gt;在 Dubbo 中，实际上提供了一组不同类型的 Cluster 对象，而每一个 Cluster 对象就代表着一种具体的集群容错机制，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-8afa6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 29.810901001112345%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABVElEQVQY0z1R207CUBDs/3+JMUYEQa5KqyKXAqUtLb1zRwo8QHiQUGWcHownmXS7u2dmd47UbNxA6+eILAZaDqvVDJYlo9+9h9q+w243R3oulx/x9dwm1M4t+r0MlssxTKMqerV+BoZeguT7KhxHJhQ4IwXb7Rph0MPIfoE1VHA47JEkCXHGOflGFBms1eE6r9hsPhGFXcYyPE/BOFIhTcY6G6rEM2yrhjheIQg6jEvMvVFgw7hIsRwmE5NiBqcqYmhWsV4vSNagcJmowPc+IM1mFvTBExNVgTheirUG2iMbWoIwDFtYzNuYTl3MZ64gNI3yP6GhFwW8lDBdwTTS6WRCoWcxAr/LXI0C7/g6nf48vCLwdZh6WpOFPZ6nctq6sMd125Bsq0KiHFfKc8UCH2XKyRQ2PPC/iONxj+u54HxOhF82a7aVF48ysmuMs7xf4J08fgHiX6YtVoMm0wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 15 16&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-fee1c.png&quot; data-srcset=&quot;/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-a67b7.png 200w,
/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-0b187.png 400w,
/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-fee1c.png 800w,
/static/2024-11-06-11-15-16-4cc43907c09cc2dcfaece887014fa6ae-8afa6.png 899w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上述方案中，Dubbo 默认使用的是 FailoverCluster。我们来看一下这个默认实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30473903825021930000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class FailoverCluster implements Cluster {
    public final static String NAME = &amp;quot;failover&amp;quot;;

    public &lt;T&gt; Invoker&lt;T&gt; join(Directory&lt;T&gt; directory) throws RpcException {
        return new FailoverClusterInvoker&lt;T&gt;(directory);
    }
}`, `30473903825021930000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FailoverCluster&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;failover&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; directory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FailoverClusterInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到该类非常简单，join 方法只是根据传入的 Directory 构建一个新的 FailoverClusterInvoker 实例。而查看其他的 Cluster 接口实现，可以发现它们的处理方式与 FailoverCluster 类似，都是返回一个新的 Invoker。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-中的集群容错机制&quot;&gt;&lt;a href=&quot;#dubbo-%E4%B8%AD%E7%9A%84%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 中的集群容错机制&lt;/h3&gt;
&lt;p&gt;显然，想要理解 Dubbo 中的集群容错机制，重点是要分析上图中所示的各种 ClusterInvoker 对象。&lt;/p&gt;
&lt;p&gt;这里，我们同样选择默认的 FailoverClusterInvoker 作为分析入口。在深入 FailoverClusterInvoker 之前，我们发现该类存在一个基类，即 AbstractClusterInvoker，而 AbstractClusterInvoker 又实现了 Invoker 接口，它们之间的关系如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-16-27-2c8d08276ffc25d296cc6f9d7dfdebea-960c5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 229px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 145.85152838427945%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAYAAACqhkzFAAAACXBIWXMAAAsSAAALEgHS3X78AAAFRklEQVRIx41VaVdaSRB9f33OTM6ZzGTGD1ld4grKoiwCD0HWxwMSVHZcQBDECIhgjDHREL1zu1/MxDOTmcC5p6qr61V3V9XtVsDf7e2tELi8/ACbdQ6qaoJfXcSan1gzZDBgod1Mu1nKwNoSTKZnaLVa8tubmxuIMMq3AY+PewiFZqnlcHoSwUkvjG4nhP5JGM2GF+dnGoaDGAb9KH2yKBasROHvgPzfC9jpnEBPWHB9lUEhZ0MqaYZ39SlCwUl43I+lFCjk7Xj/Lo3dnVWUS+WvAcUW/xEwGjVR2yFKeHuWxuA0gQ+XGe44jtN+DBfvUpzLE7sM5rgX8PbfAsajZmrbRBmZ14uYm3kA1fsE01M/YX7mFwT845wrEhVUyg6USqXvBzzpncLpeC7zdlD34/BgDa3mGpr1NRw0/MwjxwdCV9E7DmM9OMEd/kfAbrcPr3cS7y+SOOlGeUwBUYQ4jx6XuoCYu7xIMd/z3GHx+zkcDM6wsDCGrcwSC2JCOrmAVykzkvo8dG0Wr9NmaUvpC8huWmFZeoRKpXJ/h0L5Fu12G7VqDfX9OvZr+zzeAcKhMFSfKm0G9rFP1Ot1XF19lIHuvpc7/L+fWKTRaPyIK5Rmq4lCISdzUSoWqefvoVQqIJfLYnNzg/MF5PM5Y07IL3qR9iJltVqFYjI9p8HGY6io7nkofV+knw6r2Ntd5dhr2Go+1Gpe7FHfp6xVKfcNubPtIG2fQFlft+LTdQaLC7+yB6dlK8Rjs0z+PFkxDrv1EYswj1dpE1TPUxZrgTmdkIVLp+bhco7R1ySpuJFxQnG7ZtlrPn48IQOG1sdZWROCHItAAtHwJGKRl3IBLfaSwblAaoH+M9zAuJTDQZwXyBwUi2WCDaui34ujy2btdaLovDHkMeWdfofusTHf7UQkTnox2iJ40w5g1TUOxW6fQq8bxpCcHQ40nA10wtDP3ybJZ13yWDTz8FT7OifGvW7EAAOLW8ivzjCHQSuZkaKzLvMWDU8ju2VDddeDCHVdm5OUu77apPTLhhbH/TzKyQtCcNrALnNqh7Jsn+RqAXSO1+lsYmLNsmLbZTsyr0wswgwr7MLboYbanhO57BLbxYKzobh5dLw7T/AkCXz8oPMinoISDQeZ2BVW1ktuqtB1FQnNC03zkXIqCxCA17MIq2WKYz/nfBKRsJvVJiiFHlp3IBBY/TGm5PJFvM5sQPD/f5kiif35lpz8hKvrEeUI19cGhD4a3aLZamN3t0r9Bh+F353PlQHDz1hNETy12aaYKzeP7eBxnFLGYyvQ4k4e041oxM7j2ZgSl7QlKGNRY/7Ox+mYZm6zUMrlEvlpYeLdpBDfCN7CR0cB9mAQjfqqLFKr6adNXKyCcm5ZNPGA1esefueUerW6gmQyAmVnZ0e+DZsbFiyZfuNV/0A+SM7lMVgXH8Jifgjr0h+wLP6O+dkHkqLLtj8R9L+AzfKI/j9jxTom+ZzJ6CLgNo80hYvzJFsnzBYSjUp29KJ8PmMS3Y7BjL68rSNfm7pPH8GUs6GO3NYSUxKFsr1TIQdfkD4h2bhHh0EckUaHfEvazQDah9Rbxlsi5oUubC3ONeo+NHjbHLWDbOo55jZi7DDPd3b0aUu+GzJQK2Dshjs4aq9LKWh4+T4t7WJ+QBp+HmVZ5Q35CraaKtJpjTvcrvDOc8v3VkjV+5j0e8kj2JCIz8DneYxEbJqBIjhnWnRtGgH1ORcOfKGeeKSq6PfDDBiHUq3uweV6xmAqaeWQKBbchAv5nIOt4MTW5jL57eDYKW0GDF34V8oeso2tl9LwFxq/4fxGUFpdAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 16 27&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-16-27-2c8d08276ffc25d296cc6f9d7dfdebea-960c5.png&quot; data-srcset=&quot;/static/2024-11-06-11-16-27-2c8d08276ffc25d296cc6f9d7dfdebea-6f066.png 200w,
/static/2024-11-06-11-16-27-2c8d08276ffc25d296cc6f9d7dfdebea-960c5.png 229w&quot; data-sizes=&quot;(max-width: 229px) 100vw, 229px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从设计模式角度讲，AbstractClusterInvoker 采用的是很典型的模板方法设计模式。模板方法设计模式的一般实现过程就是为整个操作流程提供一种框架代码，然后再提取抽象方法供子类进行实现。上图中就展示了模板方法的设计思想。&lt;/p&gt;
&lt;p&gt;AbstractClusterInvoker 的实现逻辑也是类似，它的主要步骤包括从 Directory 获得 Invoker 列表、基于 LoadBalance 实现负载均衡，并基于 doInvoke 方法完成在远程调用中嵌入容错机制。&lt;/p&gt;
&lt;p&gt;这里的 doInvoke 就是模板方法，需要 FailoverClusterInvoker 等子类分别实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32203766530519507000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public abstract class AbstractClusterInvoker&lt;T&gt; implements ClusterInvoker&lt;T&gt; {
    protected abstract Result doInvoke(Invocation invocation, List&lt;Invoker&lt;T&gt;&gt; invokers,  LoadBalance loadbalance) throws RpcException;
}`, `32203766530519507000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractClusterInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClusterInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doInvoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token class-name&quot;&gt;LoadBalance&lt;/span&gt; loadbalance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AbstractClusterInvoker 类的代码有点长，但理解起来并不是很复杂。通过观察该类中的代码实现，可以看到存在一批以 select 结尾的方法，包括 select、doselect、reselect 以及 LoadBalance 本身的 select 方法。我们基于这些 select 方法梳理整体的处理流程，并给出如下所示的伪代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;9543628291891526000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`select() {
   checkSticky(); // 粘滞连接

   doselect() {
      loadbalance.select();

      reselect() {
         loadbalance.select();
      }
   }
}`, `9543628291891526000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;checkSticky&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 粘滞连接&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;doselect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      loadbalance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token function&quot;&gt;reselect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         loadbalance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述伪代码清晰展示了这些 select 方法的嵌套过程，从而能够更好地帮助你梳理代码执行流程。&lt;/p&gt;
&lt;p&gt;首先，select 方法的第一部分内容提供了 &lt;code class=&quot;language-text&quot;&gt;粘滞连接&lt;/code&gt; 机制。所谓粘滞连接（Sticky Connection），就是为每一次请求维护一个状态，确保始终由同一个服务提供者对来自同一客户端的请求进行响应。这点和我们在上一讲中提到的源 IP 哈希负载均衡算法比较类似，你可以做一些回顾。在 Dubbo 中，使用粘滞连接的目的是减少重复创建连接的成本，提高远程调用的效率。我们可以通过 URL 传入的 sticky 参数对该行为进行控制。&lt;/p&gt;
&lt;p&gt;处理完粘滞连接之后，select 方法就借助于 doselect 方法执行下一步操作。doselect 方法执行了一系列的判断来最终明确目标 Invoker 对象。首先，我们需要判断当前是否存在可用的 Invoker 对象，如果没有则直接返回。如果有，那么就分如下几种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果只有一个 Invoker 对象，那么该 Invoker 对象就是目标 Invoker 对象；&lt;/li&gt;
&lt;li&gt;如果有两个 Invoker 对象，则使用轮询机制选择其中一个进行返回；&lt;/li&gt;
&lt;li&gt;如果有两个以上的 Invoker 对象，这时候就会借助于 LoadBalance 的 select 方法，通过负载均衡算法来最终确定一个目标 Invoker 对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下图展示了这个执行过程：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-18-19-da847399d231ab5a15cef86a603cf718-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.884080370942815%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABHklEQVQY012Q206DQBRF+f9PURoVitSYiEafpJQOtAVKuV+qvaS3WGn6sN3ig8ZJVs5kMueclS3hz2maIwK/B9/TWHWYrzJc5xphaMDz+oiiO9iDqxbXUTAeqXCEgr4p895FVT1BapoPnM+fOJ2O2O93iKMxGWGxyDCvY9TVDMtlie12jbqeIc8DFKQspr+UIYeFeH+LIYmhQpMLblAwtF9o8Uije27uwrYU2upc8Azft+BNbkkPk7GOkavx/QFpYrBPZb+GNDUgHQ57rNcr7HZbssE0cDlEIMumSJIAOet8nmGzWdEubN+z9IeimLUkic9hAS0jSP8zFOKGBkprbJmXEMMO7z1maNJebzN1xHeOcvvPHnRg9WWaq4zHwBc4i2IMdWVp9gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 18 19&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-18-19-da847399d231ab5a15cef86a603cf718-a40ca.png&quot; data-srcset=&quot;/static/2024-11-06-11-18-19-da847399d231ab5a15cef86a603cf718-3426a.png 200w,
/static/2024-11-06-11-18-19-da847399d231ab5a15cef86a603cf718-f3a4f.png 400w,
/static/2024-11-06-11-18-19-da847399d231ab5a15cef86a603cf718-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在获取了目标 Invoker 对象之后，Dubbo 并不会直接就使用这个对象，因为我们需要考虑该对象的可用性。如果该 Invoker 对象不可用或者已经使用过，那么就需要通过 reselect 方法重新进行选择。而如果在 Invoker 列表中已经没有可用的 Invoker 对象了，那么也就只能直接使用当前选中的这个 Invoker 对象。下图进一步展示了 Invoker 对象的可用性判断逻辑：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-06-11-18-55-9e26dc5bd030d8ea83e627c27f79d33f-ab2ac.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 531px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50.470809792843696%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABuklEQVQoz3VSa1PaUBDN//8dnSl8aKuOtVUpUqe1owQNCPLKq3QgNBRTY96v08MFRDN2Mzs3e3fvuWfPXgm0oiiwtTjOcNf7AuWmgpZcRfPqDXrdBp7brr5A2aQ8z1mQI0kSBIEv1tlsKEAMXUanU8d0OhIgnucxH4v69bkCZULS9idNcziOiyhKEYYZsmy9z3MijqIEi8WSl8b/YbsB1PU2dK2J8bgJTZXZ6hkUpY7J5FYUz+caWZ6x9RMYxjVZt2AaLZ5p8SL/CXTrUvd2D/27KrqdKtTRB8ytE9jzY5j6Z8FMU79i0K9AHe+j066grbzF9NcRZtNP+OvYmy7yHWCve0rAQxhaDc79JRb2Bf4svhOgQRkKqOolRsOPBDjHeHQMufmO8RFMsw7XvS+NhICrIcRRTDYRRffh+4FYoyjeaJuJXBCE3IuQZimWS0foHYYJbHsJy/pNcI8vJN0N5TUrC75tz7I0at3gS7jAYHCFId00FEx+9iE9F7TsLwRfjZvmOA9s95SSvOdgDihRDe7DOTNtWLPGimGx+XYPdR0Vr7L1vIBsvqHfOyRgDYF/g0dXRhhcc1g/8A/BBPL6l8QrXAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 06 11 18 55&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-06-11-18-55-9e26dc5bd030d8ea83e627c27f79d33f-ab2ac.png&quot; data-srcset=&quot;/static/2024-11-06-11-18-55-9e26dc5bd030d8ea83e627c27f79d33f-a8ddc.png 200w,
/static/2024-11-06-11-18-55-9e26dc5bd030d8ea83e627c27f79d33f-0d4eb.png 400w,
/static/2024-11-06-11-18-55-9e26dc5bd030d8ea83e627c27f79d33f-ab2ac.png 531w&quot; data-sizes=&quot;(max-width: 531px) 100vw, 531px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;至于 reselect 方法，它的主要实现过程同样也是借助于 LoadBalance 的 select 方法完成对 Invoker 的重新选择。Dubbo 会使用一个标志位对传递给 LoadBalance 的 Invoker 对象的可用性进行过滤，然后将过滤之后且未被选择的 Invoker 对象列表交给 LoadBalance 执行负载均衡。&lt;/p&gt;
&lt;p&gt;以上几个方法中，只有 select 方法的修饰符是 protected 的，可以被 AbstractClusterInvoker 的各个子类根据需要进行直接调用。显然，因为 AbstractClusterInvoker 提供了模板方法，因此它的子类势必是在 doInvoke 方法中调用这些 select 方法。&lt;/p&gt;
&lt;p&gt;我们来看一下 FailoverClusterInvoker 的 doInvoke 方法，这个方法的执行逻辑同样不是很复杂。Failover 的意思很简单，就是失败重试，所以可以想象 doInvoke 方法中应该包括一个重试的循环操作。通过翻阅代码，我们确实发现了这样一个 for 循环，裁剪后的代码结构如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;80971038546435100000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`for (int i = 0; i &lt; len; i++) {
      // 由于 Invoker 对象列表可能已经发生变化，所以在执行重试操作前需要进行重新选择
      if (i &gt; 0) {
            // 验证当前 Invoker 对象是否可用
            checkWhetherDestroyed();
            // 重新获取所有服务提供者
            copyinvokers = list(invocation);
            // 重新检查这些 Invoker 对象
            checkInvokers(copyinvokers, invocation);
       }

       // 通过父类的 select 方法获取 invoker
       Invoker&lt;T&gt; invoker = select(loadbalance, invocation, copyinvokers, invoked);
       // …
       try {
            // 发起远程调用
            Result result = invoker.invoke(invocation);
            return result;
       } catch (RpcException e) {
             // 如果是业务异常，直接抛出
 }
 // …
}

// 如果 for 循环执行完毕还是没有找到一个合适的 invoker，则直接抛出异常
throw new RpcException();`, `80971038546435100000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 由于 Invoker 对象列表可能已经发生变化，所以在执行重试操作前需要进行重新选择&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 验证当前 Invoker 对象是否可用&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;checkWhetherDestroyed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 重新获取所有服务提供者&lt;/span&gt;
            copyinvokers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 重新检查这些 Invoker 对象&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;checkInvokers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;copyinvokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

       &lt;span class=&quot;token comment&quot;&gt;// 通过父类的 select 方法获取 invoker&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;loadbalance&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; copyinvokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invoked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 发起远程调用&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;token comment&quot;&gt;// 如果是业务异常，直接抛出&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 如果 for 循环执行完毕还是没有找到一个合适的 invoker，则直接抛出异常&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码中的循环次数来自于 URL 传入的重试次数，默认重试次数是 2。在重试之前，由于 Invoker 对象列表可能已经发生变化，所以需要对当前 Invoker 对象是否可用进行验证，并根据需要进行重新选择。注意到在每一次循环中，我们首先调用父类 AbstractClusterInvoker 中的 select 方法，并将该方法返回的 Invoker 对象保存到一个 invoked 集合中，表示该 Invoker 对象已经被选择和使用。&lt;/p&gt;
&lt;p&gt;一旦确定了目标 Invoker 对象，我们就可以通过该对象所提供的 invoke 方法执行远程调用。调用过程可能成功、也可能失败，而失败的结果也分两种情况，如果是业务失败则直接抛出异常，反之我们就继续执行循环。如果整个循环都结束了还是没有成功地完成调用过程，那么最终也会抛出异常。&lt;/p&gt;
&lt;p&gt;至此，基于 FailoverClusterInvoker 的集群容错机制讲解完毕。Dubbo 中的其他集群容错实现方案交由你自行进行理解和分析。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-9&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;服务容错在当下的分布式系统开发过程中已经属于是一种标准组件，所以在面试过程中也经常出现。关于服务容错的概念和实现策略，需要面试者具备针对性的知识体系。&lt;/p&gt;
&lt;p&gt;从解决思路上讲，回答这类问题可以分成两个阶段。&lt;/p&gt;
&lt;p&gt;第一个阶段是先解释什么是服务容错。我们需要明确由于存在服务自身失败以及网络瞬态等因素，为了确保服务访问过程的可靠性，服务容错是必不可少的。然后重点是要提到服务的雪崩效应，即在分布式环境下，由于服务依赖失败导致整个服务访问链路不可用。那么雪崩效应究竟是怎么形成的呢？就是因为服务没有做到容错而导致的。&lt;/p&gt;
&lt;p&gt;第二阶段，我们需要进一步掌握服务容错实现层面的知识点。对于开发人员而言，相对于原理部分的内容，具体的实现过程反而更加容易把握，多少都能回答一些。但这道题想要回答得有体系化有一定难度，能够体现出不同面试者之间的水平差异。针对集群容错的实现，包括 Failover（失效转移）、Failback（失败通知）、Failsafe（失败安全）和 Failfast（快速失败）等多种实现策略。这些策略本身都并不是很复杂，也没有太多的难点，唯一需要注意的地方在于知识点比较细而多。回答过程中需要按照一定的逻辑进行展开，避免概念之间产生混淆。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-7&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲内容对集群容错的设计思想和实现策略进行了详细的展开。集群容错的实现策略有很多，我们基于 Dubbo 给出了该框架中内置的几种实现方案的底层原理。&lt;/p&gt;
&lt;p&gt;请注意，集群容错只是服务容错的其中一种实现方式。主流的实现方式还包括服务熔断。服务熔断的实现需要引入熔断器的概念。那么，什么是熔断器？它又是如何实现服务容错的呢？在下一讲中，我们将基于 Spring Cloud 框架讨论熔断器的实现过程。&lt;/p&gt;
&lt;h1 id=&quot;服务容错：熔断器的基本结构是怎么样的？如何实现？&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E5%AE%B9%E9%94%99%EF%BC%9A%E7%86%94%E6%96%AD%E5%99%A8%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84%E6%98%AF%E6%80%8E%E4%B9%88%E6%A0%B7%E7%9A%84%EF%BC%9F%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务容错：熔断器的基本结构是怎么样的？如何实现？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们详细介绍了集群容错的概念以及实施策略。通过构建集群机制来实现服务容错是分布式服务构建过程中的一种常见技术手段，但并不是唯一的技术手段。围绕服务容错，还存在一个面试过程中经常会出现的话题，这就是本讲内容要介绍的服务熔断。&lt;/p&gt;
&lt;p&gt;服务熔断的具体表现形式是熔断器。那么，熔断器的基本结构是怎么样的？它又是如何实现的呢？本讲内容将围绕这一问题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-7&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;除了上一讲介绍的集群容错，熔断（Circuit Breaker）也是很实用的一种容错机制。在日常开发过程中，如果你使用的是像 Spring Cloud 这样的主流分布式服务框架，那么系统所产生的每一次远程调用的背后都内置了熔断机制，只是你自己可能并没有注意到，或者甚至都没有认识到有这样一个概念。&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;上述问题虽然各有侧重点，但对于熔断机制而言，考查的重点还是候选人在工程实践的基础上所掌握的技术原理。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-8&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;熔断这个概念实际上并不难理解，类似于日常生活中经常会碰到的电路自动切断机制。我们知道在电路系统中，当电流过大时保险丝就会被融化从而隔断了整个电路回路。类比分布式服务，当系统产生大量异常时，我们也应该隔断整个服务调用链路，避免服务的不断重试触发雪崩效应。&lt;/p&gt;
&lt;p&gt;那么服务熔断是如何做到的呢？我们需要明确，对于服务消费者发起的每一次远程调用请求，熔断器都需要进行监控和记录。如果调用的响应时间过长，服务熔断器就应该中断本次调用并直接返回。请注意服务熔断器判断本次调用是否应该快速失败是有状态的，也就是说它会对一段时间的调用结果进行统计，如果统计的结果触发了事先设置好的阈值（类似电路系统中保险丝的过载等级），那么服务熔断机制就会被触发，反之将继续执行后续的远程调用。&lt;/p&gt;
&lt;p&gt;通过上述分析，我们可以梳理出如下技术上的要点。&lt;/p&gt;
&lt;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;h2 id=&quot;技术体系-10&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;基于熔断器的设计理念，我们对熔断过程进行进行抽象和提炼，可以得到如下图所示的熔断器基本结构。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-08-10-28-55-e85bc7a48ba2758e768acf8ee737382f-4b0e9.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 571px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.86690017513135%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9ElEQVQoz21Ta1PaQBTN//8L7Tdn2umM2lZth8ERQhVU5KH4gAKRxCxJIBFJSMIjj9O7q8TCeGfu7Ca55+y5524kUKRpupE8fH+G+7scun9zOC19Qb22D6WfQ6d9hjUGoh4bOGmbMEkS8WE2c9G6+Q6ld4CL8x006t/Q7/0gQhkficgI15vtCAIPupaHpv4C03MI/AoW8zIs8wrJm6rt4O+EwjiOYVkWTNPEaDQSH1erJVqtIrVdwGBwiZF1A027QL/fzAh4PcdxDOf4r+UEjOkEfCSQiihawXVdeJ6P5TKi55TWGItFRFYE9N4V1pimQfUaFEXJrJLm8xUNYI4wXIi0LBu2/Zyd+FFEUSRqhkOLyAOB4yvnkvSnKvlSRa8rk+klKqpQvrfleZ5oyXEcoXodQ3ZN3p4SRiafy3iZNOm5BqlR38X97S7k4mfIhU9oP+zhunmA6dSlk0PhEWMMhmEI4jB8VXLb+k1C8ni420ej9pX2x+T3EaRO+xCPyiEp/AnHLsJ9KdF9yxPROPNlu13HmUAdFGGPj2GwPJxxAdOJTKR/IHW7Z+h0TkjZCUyjStO8pOFc0bUJRbu+zwezFMn3PB3nme5jGfpThYjLIpl+DlWtvk55HXHCr1D6/idQ8Lb5JHVdp0OCDbW8geQNE0WJWP8BNoc6m+1TqdMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 08 10 28 55&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-08-10-28-55-e85bc7a48ba2758e768acf8ee737382f-4b0e9.png&quot; data-srcset=&quot;/static/2024-11-08-10-28-55-e85bc7a48ba2758e768acf8ee737382f-b8aee.png 200w,
/static/2024-11-08-10-28-55-e85bc7a48ba2758e768acf8ee737382f-fe23d.png 400w,
/static/2024-11-08-10-28-55-e85bc7a48ba2758e768acf8ee737382f-4b0e9.png 571w&quot; data-sizes=&quot;(max-width: 571px) 100vw, 571px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，上图所展示的结构简明扼要地给出了熔断器内部所具备的状态机，该状态机包含三个状态，即 Closed（关闭）、Open（打开）和 Half-Open（半开）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Closed: 这是熔断器的默认状态。在该状态下，相当于熔断器没有发挥任何效果，所有的请求可以得到正常的响应。但是，在熔断器内部还是会对所有的调用过程进行监控，如果有异常发生则会进行不断累加。&lt;/li&gt;
&lt;li&gt;Open: 这是熔断器的打开状态。在该状态下，相当于熔断器对所有的请求进行了隔断，来自服务消费者的请求不会触达到服务的提供者。同时，在熔断器内部也会启动一个计时器，当处于该状态达到一定时间时，熔断器会进入到半开状态。&lt;/li&gt;
&lt;li&gt;Half-Open: 这是熔断器的半开状态。所谓半开，指的是熔断器会允许一部分请求通过，然后再对这些请求的响应结果进行统计。如果这些请求的成功比例达到一定的阈值，则会把熔断器设回到关闭状态，反之则进入打开状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关于熔断器的组成和状态我们已经明确了，但显然还有很多细节是缺失的。如果你只是用这些内容来回答面试问题，应该可以拿到 60 分，但剩下的 40 分才是更好地拉开你和其他候选人之间差距的关键。为了让你更好地理解熔断器的实现原理，接下来，我们讨论如何自己实现一个包含这三个状态的熔断器。&lt;/p&gt;
&lt;p&gt;首先，我们需要定义一个枚举类 State，代码如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13960211527797895000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public enum State {
   CLOSED,
   OPEN,
   HALF_OPEN
}`, `13960211527797895000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   CLOSED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   OPEN&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   HALF_OPEN
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后，我们定义代表熔断器的 CircuitBreaker 接口，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;36593139458651546000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface CircuitBreaker {
   // 成功的响应，会对熔断器状态进行重置
   void recordSuccess();
   // 失败的响应，根据需要对状态进行设置
   void recordFailure(String response);
   // 获取熔断器的当前状态
   String getState();
   // 设置熔断器状态
   void setState(State state);
   // 对远程服务发起请求
   String attemptRequest() throws RemoteServiceException;
}`, `36593139458651546000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreaker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 成功的响应，会对熔断器状态进行重置&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 失败的响应，根据需要对状态进行设置&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取熔断器的当前状态&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 设置熔断器状态&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 对远程服务发起请求&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attemptRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemoteServiceException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于 CircuitBreaker 接口的实现过程是我们讨论的重点。为了实现对熔断器状态的合理控制，我们首先需要定义一系列的中间变量，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74208327600447000000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 远程调用的超时时间
private final long timeout;
// 重试时间间隔
private final long retryTimePeriod;
// 最近一次的调用失败时间
long lastFailureTime;
// 最近一次的失败响应结果
private String lastFailureResponse;
// 累计失败次数
int failureCount;
// 失败率阈值
private final int failureThreshold;
// 熔断器状态
private State state;
// 类似无限大的一个未来时间
private final long futureTime = 1000000000000;`, `74208327600447000000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 远程调用的超时时间&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 重试时间间隔&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; retryTimePeriod&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 最近一次的调用失败时间&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; lastFailureTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 最近一次的失败响应结果&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lastFailureResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 累计失败次数&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; failureCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 失败率阈值&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; failureThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 熔断器状态&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 类似无限大的一个未来时间&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; futureTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000000000000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当我们发起一个远程调用时，我们需要基于调用结果合理设置熔断器的状态，代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;10768183830330292000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public String attemptRequest() throws RemoteServiceException {
    evaluateState();
    if (state == State.OPEN) {
      // 如果熔断式处于打开状态，就直接返回失败结果
      return this.lastFailureResponse;
    } else {
       try {
        // 执行远程调用
        var response = service.call();
        // 远程调用成功
        recordSuccess();
        return response;
      } catch (RemoteServiceException ex) {
        // 远程调用失败
        recordFailure(ex.getMessage());
        throw ex;
      }
    }
}`, `10768183830330292000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attemptRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RemoteServiceException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;evaluateState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OPEN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果熔断式处于打开状态，就直接返回失败结果&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 执行远程调用&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 远程调用成功&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;recordSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RemoteServiceException&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 远程调用失败&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;recordFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们继续来实现当调用成功和失败时，对应的 recordSuccess 和 recordFailure 方法的执行逻辑，代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84404579134133320000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 调用成功，失败次数清零，最近失败时间设置成无限大，并把熔断器状态设置成关闭
public void recordSuccess() {
    this.failureCount = 0;
    this.lastFailureTime = System.nanoTime() + futureTime;
    this.state = State.CLOSED;
}

// 调用失败，失败次数加 1，最近失败时间设置成当前时间，并把失败结果设置成最近一次调用失败的响应
public void recordFailure(String response) {
    failureCount = failureCount + 1;
    this.lastFailureTime = System.nanoTime();
    this.lastFailureResponse = response;
}`, `84404579134133320000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 调用成功，失败次数清零，最近失败时间设置成无限大，并把熔断器状态设置成关闭&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failureCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nanoTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; futureTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLOSED&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 调用失败，失败次数加 1，最近失败时间设置成当前时间，并把失败结果设置成最近一次调用失败的响应&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    failureCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failureCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nanoTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当系统运行一段时间之后，我们如何来获取熔断器的当前状态呢？可以实现如下所示的 getState 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35868059007026123000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public String getState() {
    // 如果失败次数大于阈值
    if (failureCount &gt;= failureThreshold) {
      if ((System.nanoTime() - lastFailureTime) &gt; retryTimePeriod) {
        // 如果失败的累计时间已经超过了所允许的重试时间间隔，状态即为半熔断（相当于此时已经过了重试时间，需要打开半开状态）
        state = State.HALF_OPEN;
      } else {
        // 反之，熔断器应该为开启状态
        state = State.OPEN;
      }
    } else {
      // 熔断器为打开状态
      state = State.CLOSED;
    }
    return state.name();
}`, `35868059007026123000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 如果失败次数大于阈值&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failureCount &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; failureThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nanoTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastFailureTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; retryTimePeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 如果失败的累计时间已经超过了所允许的重试时间间隔，状态即为半熔断（相当于此时已经过了重试时间，需要打开半开状态）&lt;/span&gt;
        state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HALF_OPEN&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 反之，熔断器应该为开启状态&lt;/span&gt;
        state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OPEN&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 熔断器为打开状态&lt;/span&gt;
      state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLOSED&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对应的，如果我们想要直接对熔断器状态进行设置，可以使用如下所示的 setState 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18545435983279157000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public void setState(State state) {
    this.state = state;
    switch (state) {
      case OPEN: // 设置开启状态
        this.failureCount = failureThreshold;
        this.lastFailureTime = System.nanoTime();
        break;
      case HALF_OPEN: // 设置半开状态
        this.failureCount = failureThreshold;
        this.lastFailureTime = System.nanoTime() - retryTimePeriod;
        break;
      default: // 默认为关闭状态
        this.failureCount = 0;
    }
}`, `18545435983279157000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;State&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; OPEN&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 设置开启状态&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failureCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failureThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nanoTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; HALF_OPEN&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 设置半开状态&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failureCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failureThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastFailureTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nanoTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; retryTimePeriod&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 默认为关闭状态&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failureCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里我们也只是通过对熔断器的几个核心变量设置合适的值来更新熔断器的最新状态。&lt;/p&gt;
&lt;p&gt;以上代码示例可以帮助你更好地理解一个熔断器的内部实现原理，也可以帮助你回答类似“如果让你来实现一个简单的熔断机制，你会怎么做？”这种开放式问题。但是，这个毕竟只是一个示例。要想完整理解熔断器的实现过程，还是需要我们来对主流分布式服务框架中的原理展开解析。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-8&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在 Spring Cloud 中，专门为开发人员提供了具备服务熔断功能的 Spring Cloud Circuit Breaker 组件。从命名上看，Spring Cloud Circuit Breaker 是对熔断器的一种抽象，支持不同的熔断器实现方案。在 Spring Cloud Circuit Breaker 中，内置了四种熔断器，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-08-11-09-46-3e59fa0318752c16d4262fbebc485074-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.76160990712074%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACJklEQVQoz3WSi07iYBSE+/7vsK4xa1RkRa0irCAqIJQKAoVyr+V+kchNKLAL3/6t0cRkd5pJ27SZ/5yZkTbbDV+42WCjVstRLJxTKnpREy4SyhHZjIfUo5ts+hSjdoWW9fP2tnD+3263DiX+g1o1y8vglmYjyE1wF1U5Jq/JRML7QvicyTguRMMsFutPQRtSo9PA7JrUew1qLYNWr+18WK/XmGZdCNwRDgepVnWKxSzlco5SSUPTUuI9R7NpMJtNP0WlA9WFq3DCkfYTue3jInfFYrbgdfTKdPqGrvtZWg+USx6x6iFF/aewwuYJ0/E9o9cA+VzUEbTtkk71C06qMm79lNAsylX5mn6nz3JpYRgm8fgvWi2VTPqG8L2MovhQ4u+sVGJi4giNhvFpleTJy1w0fbgLZ9zNH7gs+Om2OgxeBoxGI8f0StUQkz5QNx+FtwrViiLuCRGYECzFeDYSYuIIY7GV5MmdI9d9eMoyN5MwvtI1rXqTdqdNt9tlPB6jqrbAGZvfCWaTex4TeyixXZH4DyajW1bLGL2OX/j5jHSYcrOfc7ET3+Og6Oay6H83WFyWZTGfzxkOhxT0DNFoSIQUEp49OaFoWpJ8/slhJpOk1+sjJUspUmaap+c0ai1Jtqp9qcEHVqs/ooteMbVdpVuxrk+sGhAHeZ3n4UuMQT/y7x5+lPSDNixrhZ4P0WkHSarHBAM7IqQ90dHvBK6/USl7xQF+/gLv18pc1TAXFAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 08 11 09 46&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-08-11-09-46-3e59fa0318752c16d4262fbebc485074-0e173.png&quot; data-srcset=&quot;/static/2024-11-08-11-09-46-3e59fa0318752c16d4262fbebc485074-2fb9f.png 200w,
/static/2024-11-08-11-09-46-3e59fa0318752c16d4262fbebc485074-f1e72.png 400w,
/static/2024-11-08-11-09-46-3e59fa0318752c16d4262fbebc485074-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中的 Netflix Hystrix 显然来自于 Netflix OSS，Resilience4j 是受 Hystrix 项目启发所诞生的一款新型的容错库，Sentinel 从定位上讲是一款包含了熔断降级功能的高可用流量防护组件，而最后的 Spring Retry 是 Spring 自研的重试和熔断框架。针对以上四种熔断器，Spring Cloud Circuit Breaker 提供了统一的 API。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-circuit-breaker-组成结构&quot;&gt;&lt;a href=&quot;#spring-cloud-circuit-breaker-%E7%BB%84%E6%88%90%E7%BB%93%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Circuit Breaker 组成结构&lt;/h3&gt;
&lt;p&gt;为了在应用程序中创建一个熔断器，我们可以使用 Spring Cloud Circuit Breaker 中的工厂类 CircuitBreakerFactory，该工厂类的定义如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31365459110820094000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public abstract class CircuitBreakerFactory&lt;CONF, CONFB extends ConfigBuilder&lt;CONF&gt;&gt; extends AbstractCircuitBreakerFactory&lt;CONF, CONFB&gt; {
   public abstract CircuitBreaker create(String id);
}`, `31365459110820094000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreakerFactory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;CONF&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CONFB &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;CONF&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractCircuitBreakerFactory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;CONF&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CONFB&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreaker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这是一个抽象类，只有一个 create 方法用来创建 CircuitBreaker。CircuitBreaker 是一个接口，约定了熔断器应该具有的功能，该接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11881577885461225000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface CircuitBreaker {
   default &lt;T&gt; T run(Supplier&lt;T&gt; toRun) {
      return run(toRun, throwable -&gt; {
         throw new NoFallbackAvailableException(&amp;quot;No fallback available.&amp;quot;, throwable);
      });
   };

   &lt;T&gt; T run(Supplier&lt;T&gt; toRun, Function&lt;Throwable, T&gt; fallback);
}`, `11881577885461225000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreaker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Supplier&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; toRun&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toRun&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; throwable &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NoFallbackAvailableException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No fallback available.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; throwable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Supplier&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; toRun&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fallback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里用到了函数式编程的一些语法，但我们从方法定义上还是可以明显看出包含了 run 和 fallback 这两个方法。其中的 Supplier 包含了你希望运行在熔断器中的业务代码，而 Function 则代表着回退（Fallback）方法。如果你熟悉 Netflix Hystrix 中的 HystrixCommand，你会发现两者之间存在明显的对应关系。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Circuit Breaker 中，分别针对 Hystrix、Resilience4j、Sentinel 和 Spring Retry 这四款框架提供了 CircuitBreakerFactory 抽象类的子类。一旦在代码工程的类路径中添加了相关的 starter，系统就会自动创建 CircuitBreaker。也就是说 CircuitBreakerFactory.create 方法会实例化对应框架的一个 CircuitBreaker 实例。&lt;/p&gt;
&lt;p&gt;在引入具体的开发框架之后，下一步工作就是对它们进行配置。在 CircuitBreakerFactory 的父类 AbstractCircuitBreakerFactory 中，我们发现了如下所示的两个抽象方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70789990761757980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 针对某一个 id 创建配置构造器
protected abstract CONFB configBuilder(String id);

// 为熔断器配置默认属性
public abstract void configureDefault(Function&lt;String, CONF&gt; defaultConfiguration);`, `70789990761757980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 针对某一个 id 创建配置构造器&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CONFB&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 为熔断器配置默认属性&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configureDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CONF&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; defaultConfiguration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里用到了大量的泛型定义，我们可以猜想，在这两个抽象方法的背后，Spring Cloud Circuit Breaker 会针对不同的第三方框架提供不同的配置实现过程。我们在接下来的内容中会基于具体的框架对这一过程做展开讨论。&lt;/p&gt;
&lt;p&gt;让我们来到 Hystrix 框架，来看看在 Spring Cloud Circuit Breaker 中是如何使用统一编程模式完成对该框架的集成。我们首先关注实现了 CircuitBreaker 接口的 HystrixCircuitBreaker 类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;14273423178413892000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class HystrixCircuitBreaker implements CircuitBreaker {
   private HystrixCommand.Setter setter;

   public HystrixCircuitBreaker(HystrixCommand.Setter setter) {
      this.setter = setter;
   }

   @Override
   public &lt;T&gt; T run(Supplier&lt;T&gt; toRun, Function&lt;Throwable, T&gt; fallback) {
      HystrixCommand&lt;T&gt; command = new HystrixCommand&lt;T&gt;(setter) {
         @Override
         protected T run() throws Exception {
            return toRun.get();
         }

         @Override
         protected T getFallback() {
            return fallback.apply(getExecutionException());
         }
      };

      return command.execute();
   }
}`, `14273423178413892000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreaker&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreaker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; setter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; setter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; setter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Supplier&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; toRun&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fallback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;setter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; toRun&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fallback&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExecutionException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不难想象，这里应该构建了一个 HystrixCommand 对象，并在该对象原有的 run 和 getFallback 方法中封装了 CircuitBreaker 中的统一方法调用，而最终实现熔断操作的还是 Hystrix 原生的 HystrixCommand。&lt;/p&gt;
&lt;p&gt;然后，我们接着来看 HystrixCircuitBreakerFactory，这个类的实现过程也简洁明了，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;33049679787459140000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class HystrixCircuitBreakerFactory extends CircuitBreakerFactory&lt;HystrixCommand.Setter, HystrixCircuitBreakerFactory.HystrixConfigBuilder&gt; {
   // 实现默认配置
   private Function&lt;String, HystrixCommand.Setter&gt; defaultConfiguration = id -&gt; HystrixCommand.Setter.withGroupKey(
      HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName())).andCommandKey(HystrixCommandKey.Factory.asKey(id));

   public void configureDefault(Function&lt;String, HystrixCommand.Setter&gt; defaultConfiguration) {
      this.defaultConfiguration = defaultConfiguration;
   }

   public HystrixConfigBuilder configBuilder(String id) {
      return new HystrixConfigBuilder(id);
   }

   // 创建熔断器
   public HystrixCircuitBreaker create(String id) {
      Assert.hasText(id, &amp;quot;A CircuitBreaker must have an id.&amp;quot;);
      HystrixCommand.Setter setter = getConfigurations().computeIfAbsent(id, defaultConfiguration);
      return new HystrixCircuitBreaker(setter);
   }

   // 创建配置构造器
   public static class HystrixConfigBuilder extends AbstractHystrixConfigBuilder&lt;HystrixCommand.Setter&gt; {
      public HystrixConfigBuilder(String id) {
         super(id);
      }

      @Override
      public HystrixCommand.Setter build() {
         return HystrixCommand.Setter.withGroupKey(getGroupKey()).andCommandKey(getCommandKey()).andCommandPropertiesDefaults(getCommandPropertiesSetter());
      }
   }
}`, `33049679787459140000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreakerFactory&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreakerFactory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreakerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixConfigBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 实现默认配置&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; defaultConfiguration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSimpleName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configureDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; defaultConfiguration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;defaultConfiguration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; defaultConfiguration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixConfigBuilder&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixConfigBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 创建熔断器&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreaker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A CircuitBreaker must have an id.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; setter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConfigurations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;computeIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultConfiguration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;setter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 创建配置构造器&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixConfigBuilder&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractHystrixConfigBuilder&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixConfigBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCommandPropertiesSetter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码基本就是对原有 HystrixCommand 中关于服务分组等属性的简单封装，你可以结合接下来要介绍的 Hystrix 框架熔断机制内容做进一步理解。&lt;/p&gt;
&lt;h3 id=&quot;hystrix-熔断机制&quot;&gt;&lt;a href=&quot;#hystrix-%E7%86%94%E6%96%AD%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 熔断机制&lt;/h3&gt;
&lt;p&gt;在 Hystrix 中，最核心的就是 HystrixCircuitBreaker 接口，该接口代表了对熔断器的抽象过程，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;16997492693095606000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface HystrixCircuitBreaker {
   public boolean allowRequest();
   public boolean isOpen();
   void markSuccess();
}`, `16997492693095606000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCircuitBreaker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;allowRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;markSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到 HystrixCircuitBreaker 接口只有三个方法。在 Hystrix 中，该接口的实现类是 HystrixCircuitBreakerImpl。&lt;/p&gt;
&lt;p&gt;我们首先来看最重要的 allowRequest 方法，该方法用来判断每个请求是否可被执行。allowRequest 实际上是对 isOpen 方法做了一层封装，在通过调用 isOpen 来触发熔断器的计算逻辑之前，先根据 HystrixCommandProperties 中的配置信息来判断是否强制开启熔断器，具体实现如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74203445031089780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public boolean allowRequest() {
   if (properties.circuitBreakerForceOpen().get()) {
      return false;
   }

   if (properties.circuitBreakerForceClosed().get()) {
      isOpen();
      return true;
   }

   return !isOpen() || allowSingleTest();
}`, `74203445031089780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;allowRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;circuitBreakerForceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;circuitBreakerForceClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;isOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;allowSingleTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来的 isOpen 方法用来获取熔断器的当前状态。请注意，熔断器中关于阈值判断的一系列处理逻辑都位于该方法中，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;96487851338593710000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public boolean isOpen() {
   if (circuitOpen.get()) {
      return true;
   }

   HealthCounts health = metrics.getHealthCounts();
   // 检查是否达到最小请求数，如果未达到的话即使请求全部失败也不会熔断
   if (health.getTotalRequests() &lt; properties.circuitBreakerRequestVolumeThreshold().get()) {
      return false;
   }

   // 检查错误百分比是否达到设定的阀值，如果未达到的话也不会熔断
   if (health.getErrorPercentage() &lt; properties.circuitBreakerErrorThresholdPercentage().get()) {
      return false;
   } else {
      // 如果错误率过高, 进行熔断，并记录下熔断时间
      if (circuitOpen.compareAndSet(false, true)) {
         circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
         return true;
      } else {
         return true;
      }
   }
}`, `96487851338593710000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;circuitOpen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;HealthCounts&lt;/span&gt; health &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHealthCounts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 检查是否达到最小请求数，如果未达到的话即使请求全部失败也不会熔断&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;health&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTotalRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;circuitBreakerRequestVolumeThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 检查错误百分比是否达到设定的阀值，如果未达到的话也不会熔断&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;health&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getErrorPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;circuitBreakerErrorThresholdPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果错误率过高, 进行熔断，并记录下熔断时间&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;circuitOpen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compareAndSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         circuitOpenedOrLastTestedTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后，我们来到用来关闭熔断器的 markSuccess 方法。显然，该方法在熔断器处于半开状态下进行使用，我们可以通过该方法将熔断器设置为关闭状态。这时候，熔断器要做的一件事情就是重置对请求的统计指标，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;57930706752660185000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public void markSuccess() {
   if (circuitOpen.get()) {
      if (circuitOpen.compareAndSet(true, false)) {
         metrics.resetStream();
      }
   }
}`, `57930706752660185000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;markSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;circuitOpen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;circuitOpen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compareAndSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         metrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resetStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HystrixCircuitBreaker 接口的这三个方法的执行逻辑实际上都不复杂，HystrixCircuitBreaker 通过一个 circuitOpen 状态位控制着整个熔断判断流程，而这个状态位本身的状态值则取决于系统目前的运行时数据。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-10&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;在分布式系统开发过程中，服务熔断的实现需要引入熔断器的概念，事实上像 Spring Cloud 等很多开源框架都内置了熔断器的实现组件。我在面试中发现很多开发人员已经在使用这些组件了，但并不清楚其背后的设计思想和原理。所以，这道题也是比较典型的从应用到原理的反向考查类的面试题。&lt;/p&gt;
&lt;p&gt;从解题思路上讲，目前业界主流的熔断器实现模型基本都是类似的。就知识体系而言，要注意的是在熔断器内部实现过程中，并不是只有熔断和非熔断这两个简单状态，而是会引入一个半熔断的状态，通过对当前服务所处理请求的情况进行统计分析，熔断器会基于一定的阈值动态完成这些状态之间的自动切换。&lt;/p&gt;
&lt;p&gt;另一方面，面试官在考查候选人能力的一大关注点还是实践能力，所以可以结合具体工具的使用过程来阐述熔断器的核心概念。在日常开发过程中，熔断器的使用方式主要是依赖于一系列的配置项。例如基于 Resilience4j 来使用 Spring Cloud Circuit Breaker 时，典型的配置项如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;65613068420997100000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`resilience4j:
   circuitbreaker:
      configs:
         default:
            ringBufferSizeInClosedState: 10 # 熔断器关闭时的缓冲区大小
            ringBufferSizeInHalfOpenState: 4 # 熔断器半开时的缓冲区大小
            waitDurationInOpenState: 20000 # 熔断器从打开到半开需要的时间
            failureRateThreshold: 80 # 熔断器打开的失败阈值
            recordExceptions: # 记录的异常
               - com.example.resilience4j.exceptions.BusinessBException
               - com.example.resilience4j.exceptions.BusinessAException
            ignoreExceptions: # 忽略的异常
               - com.example.resilience4j.exceptions.BusinessAException
      instances:
         aServiceCircuitBreaker:
            baseConfig: default
         bServiceCircuitBreaker:
            baseConfig: default
            waitDurationInOpenState: 5000
            failureRateThreshold: 20`, `65613068420997100000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;yaml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;yaml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-yaml line-numbers&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;resilience4j&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;circuitbreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;ringBufferSizeInClosedState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10 &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# 熔断器关闭时的缓冲区大小&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;ringBufferSizeInHalfOpenState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4 &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# 熔断器半开时的缓冲区大小&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;waitDurationInOpenState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20000 &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# 熔断器从打开到半开需要的时间&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;failureRateThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80 &lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;# 熔断器打开的失败阈值&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;recordExceptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 记录的异常&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; com.example.resilience4j.exceptions.BusinessBException
               &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; com.example.resilience4j.exceptions.BusinessAException
            &lt;span class=&quot;token key atrule&quot;&gt;ignoreExceptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 忽略的异常&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; com.example.resilience4j.exceptions.BusinessAException
      &lt;span class=&quot;token key atrule&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;aServiceCircuitBreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;baseConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
         &lt;span class=&quot;token key atrule&quot;&gt;bServiceCircuitBreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;baseConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default
            &lt;span class=&quot;token key atrule&quot;&gt;waitDurationInOpenState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;failureRateThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些配置信息可以分为两部分，一部分是默认配置，一部分是专属于某一个服务的特定配置。这里也展示了用来打开熔断器的失败阈值 failureRateThreshold、熔断器从打开到半开需要的时间 waitDurationInOpenState 等配置参数，以及针对异常类型的精细化控制过程。&lt;/p&gt;
&lt;p&gt;最后，在回答这道题时，一种比较好的解题思路是结合某个熔断器开源框架来分析它的内部实现原理，这是一个很大的加分项。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-8&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;服务熔断在分布式服务构建过程中是一种标配组件，例如在 Spring Cloud 中专门提供了一个 SpringCloudApplication 注解（如下所示），而在该注解中就把用来启用熔断器的 @EnableCircuitBreaker 和用来启用服务发现机制的 @EnableDiscoveryClient 以及 Spring Boot 的基础注解 @SpringBootApplication 放在一起，确保所有分布式服务都内置了服务熔断机制。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31649502673422193000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication`, `31649502673422193000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableDiscoveryClient&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableCircuitBreaker&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringCloudApplication&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;服务熔断的概念和原理理解起来都有一定的复杂度。在本讲内容中，我们从熔断器的基本结构开始讲起，详细分析了它在 Spring Cloud 的实现过程。同时，我们还自己实现了一个简单而又完整的熔断器示例，帮助你对服务熔断的实现过程有更为感性的认识。就算你平时没有专门针对熔断器做过系统学习，相信通过本讲内容的介绍也能帮助你顺利应对技术面试。&lt;/p&gt;
&lt;p&gt;掌握了服务熔断机制之后，下一讲我们将讨论另一个确保服务高可用的技术组件，即服务降级。和服务熔断一样，我们也可以采用不同的技术手段来对服务进行降级。那么，服务降级的常见实现策略有哪些呢？我们下一讲再聊。&lt;/p&gt;
&lt;h1 id=&quot;服务降级：服务降级的常见实现策略有哪些？&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7%EF%BC%9A%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7%E7%9A%84%E5%B8%B8%E8%A7%81%E5%AE%9E%E7%8E%B0%E7%AD%96%E7%95%A5%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务降级：服务降级的常见实现策略有哪些？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们介绍了基于熔断器的设计思想和实现方式。和集群容错一样，服务熔断也是一种常见的服务容错机制，也可以用来确保服务的可用性。而为了确保核心服务的可用性，有时候我们就会故意对那些不重要的服务执行下线操作，从而确保系统中有限的资源都应用到核心服务上。这就引出了在分布式系统构建过程中非常重要的一个技术组件，即服务降级。&lt;/p&gt;
&lt;p&gt;那么，什么是服务降级？又有那些常见的服务降级实现策略呢？这是面试过程中的一个常见考点，也是本讲内容的重点。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-8&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;我们先来看这个问题的背景。举个例子，在下图中存在服务 A 和服务 B 这两个服务。其中服务 B 是核心服务，而服务 A 是可以降级的非核心服务，服务 B 会依赖服务 A。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-52-03-724d798084079b600e15956555628fee-1ae11.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 485px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 16.49484536082474%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAzUlEQVQI1x2N207CUBRE+/8fwoMvYtFSWhMNFyPCg+Ipl94lWEvh2DbURJvYxaGTTDI7mdlLm051NmuDyajDy/OVyn18b8Di7RYpj5RliRCCKIqI45iqOjGf3eC7pupZRME9gW/hqp0j7tDCwAQE7qZLGPQoi5G6HarTE7ZlUhQFSZKQpqkCSMLQ43Nnq46HPDzyERvk38N2c7G2WvX5+52zXuqKZCDer8nlGHkcs3QEF9V13bppIMv2Cjyg+X9l//XAbmtzyIbtj59qxhl2utaq6jY/5AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 52 03&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-52-03-724d798084079b600e15956555628fee-1ae11.png&quot; data-srcset=&quot;/static/2024-11-12-10-52-03-724d798084079b600e15956555628fee-c8342.png 200w,
/static/2024-11-12-10-52-03-724d798084079b600e15956555628fee-f73ef.png 400w,
/static/2024-11-12-10-52-03-724d798084079b600e15956555628fee-1ae11.png 485w&quot; data-sizes=&quot;(max-width: 485px) 100vw, 485px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;现在，当访问服务 B 的流量迎来高峰时，系统中的资源即将被耗尽，服务 B 的可用性即将出现问题。这时候，我们可以对服务 A 执行降级操作，也就说把服务 A 下线。通过这种方式，原本被服务 A 所占用的资源就可以用来处理面向服务 B 的请求。但是，问题就来了，服务 A 一旦下线，服务 B 在访问服务 A 时应该如何处理呢？基本的策略就是“快速失败”，即在服务 B 向服务 A 执行调用的链路中直接获取一个返回值，而不执行真正的远程调用。&lt;/p&gt;
&lt;p&gt;显然，想要实现上述效果并不容易，我们需要考虑如何在服务 A 已经下线的情况下，确保服务 B 能够得到一个快速失败的结果。由此，我们可以引出与服务降级相关的一系列问题，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统中存在很多服务，你怎么判断哪些服务是可以降级的呢？&lt;/li&gt;
&lt;li&gt;快速失败的表现形式有哪些？&lt;/li&gt;
&lt;li&gt;想要实现服务降级，你有哪些设计思路？&lt;/li&gt;
&lt;li&gt;什么是服务回退？如何实现服务回退？&lt;/li&gt;
&lt;li&gt;Dubbo 框架是如何实现服务降级的？&lt;/li&gt;
&lt;li&gt;Spring Cloud 框架是如何实现服务降级的？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述问题都是面试官在面试过程中经常会提出的考查点，我们需要对这些考查点进行系统分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-9&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;所谓服务降级，是指在某些系统核心服务的访问压力剧增的情况下，根据当前业务情况及流量对一些服务进行有策略的快速失败处理，以此避免服务之间的调用依赖影响到其他核心服务。&lt;/p&gt;
&lt;p&gt;在面试过程中，首先需要用自己的语言对服务降级的基本概念和设计理念进行阐述，我们可以结合问题背景部分所举的例子进行展开。&lt;/p&gt;
&lt;p&gt;应对这类问题的第二点思路是对服务分级机制的讨论。既然服务可以降级，那么势必需要对服务进行分级。通常，等级越低的服务越应该被降级。我们可以结合日常的场景对服务等级进行分析。&lt;/p&gt;
&lt;p&gt;第三点，也是最重要的一点，是服务降级的实现策略。目前，在主流的开源框架中，关于如何实现服务降级有两大类策略，即：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模拟（Mock）机制&lt;/li&gt;
&lt;li&gt;回退（Fallback）机制&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;h2 id=&quot;技术体系-11&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;通过前面内容的介绍，我们已经理解了服务降级的基本概念和设计思想。从技术体系而言，关于服务降级我们要明确两个维度的内容。第一个维度是如何对服务进行分级管理，另一个维度就是具体的降级实现策略。&lt;/p&gt;
&lt;p&gt;我们先来讨论第一个维度。服务降级在实现上一般需要对业务的重要性进行区分，也就是需要明确服务分级，具体的服务分级方法因业务场景和需求而定。对每个服务进行等级管理之后，降级操作一般是从等级最低的的服务开始。&lt;/p&gt;
&lt;p&gt;关于服务分级，业界并没有统一的标准。在本讲中，我们介绍一种三级分类方法，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-54-08-68452dbd5e5f99c40f69fed06b80b64f-3e989.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 605px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 45.123966942148755%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABs0lEQVQoz4VSa2/aQBD0//8fUSuFoJAn5UsVkqpV04DB+AGGxO/zC1xwaCERTMdHUBUpUU5aza1udz07YwUvZ7vdvsK3ThAYULs1aP0GundH6Hbq6Pw6wt1tDWO7CSHaUKoB+9hsNrJxuXxEmgwRhTpjgCgaYbFYYqBdYWIfM05hDxtwHi5QzNooF9/w988PPD/dQnmLie/dYzRsoqeeoEMmtn2D1eoJ8/lvZFmMPE+IicTZLENR5JhOU6RpDKW6ZJngY4wkCVGWJYtmCENfFldN83khP1QUCVxXhef1uf5AbhD4GlynJ7HaSjH0M1hmgxqcwzROEMcx7JGBWHyVuWVyxfEVhz1SsxY8t868Dq13AFP/xNUPKUENlvEZ69UNlDQNqZHDAS5ZOWwsSD2F49gy97wJslRIhnkeQEQaa3UOVimNyhqN7+YOM+t9DYdWE/rggkacYjz+jvX6WcoRRRHdFBwqJFZ5GIYSYxFXLm+wj/8ul2RpvujVZePOZU1rU45jynNG3S5RTK+5Pu8Pl7ifnNPpnzuG+9/mo//QcVS6foB+75AmfEEYtOATA78l8wr/AcL5llKFF1KeAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 54 08&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-54-08-68452dbd5e5f99c40f69fed06b80b64f-3e989.png&quot; data-srcset=&quot;/static/2024-11-12-10-54-08-68452dbd5e5f99c40f69fed06b80b64f-4bd12.png 200w,
/static/2024-11-12-10-54-08-68452dbd5e5f99c40f69fed06b80b64f-7acd9.png 400w,
/static/2024-11-12-10-54-08-68452dbd5e5f99c40f69fed06b80b64f-3e989.png 605w&quot; data-sizes=&quot;(max-width: 605px) 100vw, 605px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;围绕服务分级，我们给出一个案例。下图展示了在移动医疗系统中基于服务分级思想所构建的无线网关业务，无线网关用于上传来自无线网络的数据并进行相应的处理。对于这样一个网关系统而言，网关的连接管理和数据上报是优先级最高的业务功能，因为缺少这些功能，网关也就不能正常工作。数据上报之后，对数据进行分析是收集网关数据的目的所在，但数据分析服务可以离线处理，并不一定需要保证服务 100% 的在线率。其他的诸如服务监控、反垃圾和后台配置则属于辅助性功能，相应的优先级也就最低。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-54-44-4befbce4cd39f6ea0cea89d1b42c3188-4c6f5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 607px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.61120263591433%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABrUlEQVQoz31SXXOaUBTk//+P9MHRfNS0yUNf7INNaxU0KB8iQVFBBPlQo8kEt8ulydg045nZOWe4e/eePQcJR3E4HASKsG0Vun6BTrsiMOifw3G0v7z8jfseEt7Fq2CaRAiXOh7sjkAQaMiy+B/ORyF9JJZlCXxvgjBcwDQHhCbq4ltxduzmZIeFldJuC6ZRQ1+t0e4n5irs0TVzBc5DW3Dy/OW04PH81uuUFl1EkYfx2MKEKOpgMcVmk+FUSOWAX4dcdhivfMxnKjxP42LuYFm/sVyamLo9rFae4Dw+biieYLtN3/J6nfy/lCIM4w66doGuXIXSqWJk3dDyLQYcwchqwfcj/GheQm6fofXzDP37KnpKBep9DZJhNGDoDV5ocvgNKMp3dsMNhxGSJIPrzgTiOKXtGPv9TjwaRQHm8xF63V90oHJhDha+DUnuXKGr1GGZ36DIV2IhRYShg2Vg8jFeGLY5P4MPjctfKg1pf0CRIc9k1hpmUx273Q7S09MLnp9zFHlPlEvZwp00Sf4CrX/NfEvhOhfUJC+nkMWRfGYj5zyvY2h+pd1Ldj3DH4aZ6/PyNmHdAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 54 44&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-54-44-4befbce4cd39f6ea0cea89d1b42c3188-4c6f5.png&quot; data-srcset=&quot;/static/2024-11-12-10-54-44-4befbce4cd39f6ea0cea89d1b42c3188-3bd24.png 200w,
/static/2024-11-12-10-54-44-4befbce4cd39f6ea0cea89d1b42c3188-52f0c.png 400w,
/static/2024-11-12-10-54-44-4befbce4cd39f6ea0cea89d1b42c3188-4c6f5.png 607w&quot; data-sizes=&quot;(max-width: 607px) 100vw, 607px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;显然，这里我们以对用户的影响程度为标准对服务等级进行了划分。&lt;/p&gt;
&lt;p&gt;介绍完服务等级的划分方式，我们来讨论第二个维度，即服务降级的实现策略。在问题分析部分，我们已经提到了两种主流的降级机制，即模拟和回退。&lt;/p&gt;
&lt;p&gt;Mock 并不是一个新概念，通常用于测试领域。首先，非常明确的一点，Mock 机制的作用就是完成对系统中组件与组件之间的有效隔离。在服务访问过程中，我们通常关注的是目标服务本身的功能和行为，对于该服务涉及到的一些依赖，我们仅仅关注它们在整个服务访问中的交互过程，比如是否调用、何时调用、调用的参数和返回值、调用过程中出现的各种异常等。至于调用过程中具体执行的业务逻辑，对于调用结果而言并不重要。&lt;/p&gt;
&lt;p&gt;基于这种设计思想，我们就可以通过 Mock 对象来模拟真实对象的调用结果，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-55-25-d9062a55a2fe7b64a1c8fef7eebaa436-8d56d.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 465px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.72043010752689%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABcUlEQVQoz41RaWvCQBDN//8VBWtBi2I19UDbgFbrgdbbaGpMFRNNNCZaj3jkdXdFof1QHHjMzLLz5u1bDjfEbreDbdsM6fQbmo0wNDWJ/Ps9inkPyqUHGLoAZRAFt1x+Yz43sVhYMM0FyQvWm6YFy7Ixm81ItrDZbOC6LlswnWro93uQZQmCkIQkiayW+xK4ajVGmFMQO1FIvTi6YhxD5QW1Kk8Gp1iv14xU13WyxIRhGGzpJRxn/+s13EclhGb9EdnMHUpFL/I5D8R2EK1mmBCdh0ejERRFwWAwgKZpGA6/cDjscTqdiPI1U+66JwbOMHSo6giTyfgK2muaiu12y0A9PB6PjIBmGqQkpC5WK2rFuaZn3C2fQhU4jsPIOm0RuSxP7BHQqCeINTHUawli1Ssq5Rg4epluvkhmuPbuFZeYTAyo4zQO+wLkzwi6nSDarQCcXYG8LPm/wgvRX+JMJoWnkBd8xI9I2EeyD8+8nyCAH+UVVJ3GViZbAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 55 25&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-55-25-d9062a55a2fe7b64a1c8fef7eebaa436-8d56d.png&quot; data-srcset=&quot;/static/2024-11-12-10-55-25-d9062a55a2fe7b64a1c8fef7eebaa436-afa68.png 200w,
/static/2024-11-12-10-55-25-d9062a55a2fe7b64a1c8fef7eebaa436-5df89.png 400w,
/static/2024-11-12-10-55-25-d9062a55a2fe7b64a1c8fef7eebaa436-8d56d.png 465w&quot; data-sizes=&quot;(max-width: 465px) 100vw, 465px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;服务降级的另一种主流实现策略是服务回退。为了实现对服务的降级，服务端会准备一个本地的 Fallback 函数，该函数会在每次调用时返回一个缺省值，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-55-54-fa5021862418e70a83c318795695a04e-3e688.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 589px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.33276740237691%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABRUlEQVQoz4VRa0/CMBTd//8bhmDExFfAL4p+ANlkIKAOl4jL3ltZ2yUbhNd2bGti4he5yeltb9tz77lXq+sa/6EsSxRFgTzPla+qCtKiKIJpmhiPRjAMA3Ecq7iGI5amKWzbhmVZ8H3/z11RlPA8H1lGf2MaYxRLkoDzDJwtwQQIiYVnCoQQccfVnlKqSIPAR86p8B6cr08MnnrIlql4x6D1ew2Q5A7mc0OU38Tb6zkCvwN9cI39vsbhsFeZpXyxCpIID90mPLeN2eQMQ/HP0E/gOm3oegvaeHQjsumYvFxgKmB/3ILRPvq9K1ERV2Syb5JQku92B1jvjwiDe8ymlzAGpzCHLYR+F9NJR0rmQkaoepUkqWhugjCMFKTU7XaL9XqN1WqFzWajQClT0uUgkuQH8izjR4cyn8/hui4WiwUcxzn2HN9gnAq1W4gaOAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 55 54&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-55-54-fa5021862418e70a83c318795695a04e-3e688.png&quot; data-srcset=&quot;/static/2024-11-12-10-55-54-fa5021862418e70a83c318795695a04e-c6c0d.png 200w,
/static/2024-11-12-10-55-54-fa5021862418e70a83c318795695a04e-69d2c.png 400w,
/static/2024-11-12-10-55-54-fa5021862418e70a83c318795695a04e-3e688.png 589w&quot; data-sizes=&quot;(max-width: 589px) 100vw, 589px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;源码解析-9&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;接下来，我们就将基于 Dubbo 和 Spring Cloud 这两款开源框架分别给出服务降级的实现方式。虽然这两种框架采用不同的实现方式，但其设计思想本质上是类似的。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-中的服务降级&quot;&gt;&lt;a href=&quot;#dubbo-%E4%B8%AD%E7%9A%84%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 中的服务降级&lt;/h3&gt;
&lt;h4 id=&quot;dubbo-中的-mock-机制&quot;&gt;&lt;a href=&quot;#dubbo-%E4%B8%AD%E7%9A%84-mock-%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 中的 Mock 机制&lt;/h4&gt;
&lt;p&gt;Dubbo 通过 Mock 来实现服务降级的过程和测试领域的 Mock 有异曲同工之处。在 Dubbo 中，可以在配置服务引用时提供 Mock 机制，存在几种配置方法，这里参考 Dubbo 官网中的示例。&lt;/p&gt;
&lt;p&gt;首先，我们可以在配置文件中添加如下所示的配置项：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;59180458409809125000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference interface=&amp;quot;com.foo.BarService&amp;quot; mock=&amp;quot;true&amp;quot; /&gt;`, `59180458409809125000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.foo.BarService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果采用这种配置，那么该 Mock 类的命名必须是接口名 + Mock，在这个示例中，即 BarServiceMock。我们可以提供如下所示的实现：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50469776535621060000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class BarServiceMock implements BarService {
   public String sayHello(String name) {
      return &amp;quot;降级数据&amp;quot;;
   }
}`, `50469776535621060000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BarServiceMock&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BarService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;降级数据&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;同时，我们也可以提供如下所示的配置项，指定 Mock 的实现类。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82653464133817830000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference interface=&amp;quot;com.foo.BarService&amp;quot; mock=&amp;quot;com.foo.BarServiceMock&amp;quot; /&gt;`, `82653464133817830000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.foo.BarService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.foo.BarServiceMock&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当然，如果 Mock 方法的逻辑非常简单，我们也可以直接将实现写在配置项中。例如，如下所示的配置标明 Mock 方法直接返回 null。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35829007253595947000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference interface=&amp;quot;com.foo.BarService&amp;quot; mock=&amp;quot;return null&amp;quot; /&gt;`, `35829007253595947000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.foo.BarService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;return null&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过上述配置方法，在服务调用过程中，返回的就是一个事先提供的 Mock 对象，而不会对服务提供者发起真实请求。&lt;/p&gt;
&lt;p&gt;Mock 还存在一些高级用法，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-10-58-14-d99cff89f273ccc6dbdb64db5c1b4c47-73362.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 609px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 27.914614121510674%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABWUlEQVQY0zVRa0/CQBDs//8TJkTBRNQPajDxgxpeBaSFFmmh1LZ3fdAWCIgmkBbG7TVeMrd3s5PNzq4Ecc7iznPAXnQw6Fcx0eswzR407Q2fkzsY0wcoHzdoNa8gd2tQhnWohE67ClW5xZBytvUEKcuAAodDTjggCBjC0MNyyfH7s8dut6a/gyThxHnwfYf+LqKo0DDx/o9xzCAxrwfGSkRhREkLaTLGKtVI4FIBE7vtlArqlB9hGY1FjJcaAl+BzxXBbdZT0hmQJvor5rMmXEemopxEBr7sDjxXRhjYcBydiqnwvD4sqy1GYs1bpOliTlFVXjAzW+RsSLoRpNOpnN3xmBOO4JzshExY3u+/sd1uyB4jcYg0DYn3qSNOHCcHPlariNxESOJA8FK5kHIpWXbGYtHGoFeBPr4WSyk6YN4j4Zm6btDwa+jLl2i+X9ByKjCNe5ErwFkDf1lgr9diZ/2uAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 10 58 14&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-10-58-14-d99cff89f273ccc6dbdb64db5c1b4c47-73362.png&quot; data-srcset=&quot;/static/2024-11-12-10-58-14-d99cff89f273ccc6dbdb64db5c1b4c47-06e0b.png 200w,
/static/2024-11-12-10-58-14-d99cff89f273ccc6dbdb64db5c1b4c47-70531.png 400w,
/static/2024-11-12-10-58-14-d99cff89f273ccc6dbdb64db5c1b4c47-73362.png 609w&quot; data-sizes=&quot;(max-width: 609px) 100vw, 609px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在执行 Mock 的过程中，如果我们希望抛出一个异常而不是返回正常的 Mock 值，那么可以使用 throw 配置项。而上图中的 fail 配置项用于指定当远程调用过程中发生错误时才会返回 Mock 对象。对应的，force 配置项用于指定在任何情况下都将返回 Mock 对象。&lt;/p&gt;
&lt;p&gt;现在，让我们回到 Dubbo 中的 Cluster 接口。我们知道该接口存在一批实现类。在这些实现类中，存在一个命名上比较特殊的 MockClusterWrapper 类，该类恰恰就是用于实现 Mock 机制，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;65052742779324540000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class MockClusterWrapper implements Cluster {
   private Cluster cluster;

   public MockClusterWrapper(Cluster cluster) {
      this.cluster = cluster;
   }

   public &lt;T&gt; Invoker&lt;T&gt; join(Directory&lt;T&gt; directory) throws RpcException {
      return new MockClusterInvoker&lt;T&gt;(directory, this.cluster.join(directory));
   }
}`, `65052742779324540000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockClusterWrapper&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockClusterWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cluster &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; directory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockClusterInvoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directory&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;与其他 Cluster 接口的实现类不同，MockClusterWrapper 内部同时持有一个 Cluster 接口，相当于是对 Cluster 接口的一种包装（Wrapper），所以该类取名为 MockClusterWrapper。而该类的 join 方法中即根据传入的 Directory 构建一个 MockClusterInvoker 类。显然，Mock 的核心逻辑应该位于 MockClusterInvoker 类中，让我们来一起看一下。&lt;/p&gt;
&lt;h4 id=&quot;mockinvoker-和-mockclusterinvoker&quot;&gt;&lt;a href=&quot;#mockinvoker-%E5%92%8C-mockclusterinvoker&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MockInvoker 和 MockClusterInvoker&lt;/h4&gt;
&lt;p&gt;同样，我们也知道在 Dubbo 中存在一批 ClusterInvoker 实现类。在这些实现类中，构造函数只传入一个 Directory 对象。而 MockClusterInvoker 的构造函数则包含两个参数，除了 Directory 对象还有一个 Invoker 对象。因此，MockClusterInvoker 天生就包含了 Invoker 对象。&lt;/p&gt;
&lt;p&gt;MockClusterInvoker 的 invoke 方法执行流程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;21978712710845526000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Result invoke(Invocation invocation) throws RpcException {
   Result result = null;
   // 获取输入参数
   String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
   if (value.length() == 0 || value.equalsIgnoreCase(&amp;quot;false&amp;quot;)) {
      // 如果没有 Mock 键，则不执行 Mock
      result = this.invoker.invoke(invocation);
   } else if (value.startsWith(&amp;quot;force&amp;quot;)) {
      // …
      // 如果以 force 开头，直接 Mock，不发起远程调用请求
      result = doMockInvoke(invocation, null);
   } else {
      // 如果以 fail 开头，进入失败 Mock，即正常发起远程调用请求，如果失败则抛出了非业务异常
      try {
         result = this.invoker.invoke(invocation);
      } catch (RpcException e) {
         if (e.isBiz()) {
            // 如果是业务异常，直接抛出
            throw e;
         } else {
            //…
            // 如果捕获到非业务异常，则调用 doMockInvoke 方法返回结果
            result = doMockInvoke(invocation, e);
         }
      }
   }
   return result;
}`, `21978712710845526000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取输入参数&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; directory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MOCK_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FALSE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果没有 Mock 键，则不执行 Mock&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;force&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果以 force 开头，直接 Mock，不发起远程调用请求&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doMockInvoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果以 fail 开头，进入失败 Mock，即正常发起远程调用请求，如果失败则抛出了非业务异常&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isBiz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果是业务异常，直接抛出&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;//…&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果捕获到非业务异常，则调用 doMockInvoke 方法返回结果&lt;/span&gt;
            result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doMockInvoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码实际上就是三个分支流程，分别对应没有 Mock 配置、以 force 开头的配置和以 fail 开头的配置这三种场景。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果没有 Mock 配置，就不执行 Mock；&lt;/li&gt;
&lt;li&gt;如果是以 force 开头，那么就直接返回 Mock 对象而不发起远程调用请求；&lt;/li&gt;
&lt;li&gt;如果以 fail 开头，意味着进入失败 Mock 处理流程，即正常发起远程调用请求，如果失败则抛出了非业务异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为 MockClusterInvoker 中自身包含有一个 Invoker 对象，因此直接就可以通过该 Invoker 对象执行远程调用。如果这个异常是业务异常，就直接抛出交由上游代码进行处理。而如果捕获到非业务异常，则会调用 doMockInvoke 方法返回结果。&lt;/p&gt;
&lt;p&gt;接下来就需要看一下这个 doMockInvoke 方法的执行逻辑。该方法核心逻辑之一是获取 MockInvoker 并执行它的 invoke 方法，这部分实现如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;3415996531735721000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`List&lt;Invoker&lt;T&gt;&gt; mockInvokers = selectMockInvoker(invocation);

if (mockInvokers == null || mockInvokers.size() == 0) {
   minvoker = (Invoker&lt;T&gt;) new MockInvoker(directory.getUrl());
} else {
   minvoker = mockInvokers.get(0);
}

result = minvoker.invoke(invocation);`, `3415996531735721000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; mockInvokers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;selectMockInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mockInvokers &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; mockInvokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   minvoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MockInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   minvoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mockInvokers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; minvoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们看到，这里会通过 selectMockInvoker 方法获取 Mock 类型的 Invoker。而如果没有找到想要的 Invoker，则会自己创建一个 MockInvoker。&lt;/p&gt;
&lt;p&gt;MockInvoker 中最重要的就是 invoke 方法，该方法包含了一系列判断，核心逻辑如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62894837036516390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Result invoke2(Invocation invocation) throws RpcException {
   // 如果是空的 return 配置
   if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {
      // 直接返回空的 RpcResult
   } else if (mock.startsWith(Constants.RETURN_PREFIX)) { // 如果是包含返回值的 return 配置
      // 解析 Mock 对象，构建 RpcResult 并返回
   } else if (mock.startsWith(Constants.THROW_PREFIX)) { // 如果是 throw 配置
      if (condition) {
         // 抛出 Mock 异常
      } else {
         // 抛出业务异常
      }
   } else { // 如果是自定义 Mock 类
      // 调用 Mock 类的 invoke 方法并返回
      Invoker&lt;T&gt; invoker = getInvoker(mock);
      return invoker.invoke(invocation);
   }
}`, `62894837036516390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 如果是空的 return 配置&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RETURN_PREFIX&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 直接返回空的 RpcResult&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RETURN_PREFIX&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 如果是包含返回值的 return 配置&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 解析 Mock 对象，构建 RpcResult 并返回&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;THROW_PREFIX&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 如果是 throw 配置&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 抛出 Mock 异常&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 抛出业务异常&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 如果是自定义 Mock 类&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 调用 Mock 类的 invoke 方法并返回&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段方法针对 return 配置和 throw 配置的处理都比较简单，而如果是业务异常我们也是采用直接抛向上游代码。这里的关键是如何获取自定义 Mock 类的实例，这里用到了 getInvoker 方法，该方法的核心就是如下所示的这两行代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;56784475329934490000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`T mockObject = (T) mockClass.newInstance();
invoker = proxyFactory.getInvoker(mockObject, (Class&lt;T&gt;) serviceType, url);`, `56784475329934490000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; mockObject &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; mockClass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; proxyFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mockObject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; serviceType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里看到了熟悉的 ProxyFactory 接口，这是 Dubbo 中实现动态代理的核心接口。现在我们明确了，上述代码基于反射机制获取自定义 Mock 类实例，然后通过动态代理创建 Invoker。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-中的服务降级&quot;&gt;&lt;a href=&quot;#spring-cloud-%E4%B8%AD%E7%9A%84%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud 中的服务降级&lt;/h3&gt;
&lt;p&gt;和 Dubbo 相比，Spring Cloud 采用了另一种完全不同的机制来实现服务降级，这就是回退机制。在接下里的内容中，我们将对回退机制的使用方式和设计理念做一定分析。&lt;/p&gt;
&lt;h4 id=&quot;spring-cloud-中的回退机制&quot;&gt;&lt;a href=&quot;#spring-cloud-%E4%B8%AD%E7%9A%84%E5%9B%9E%E9%80%80%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud 中的回退机制&lt;/h4&gt;
&lt;p&gt;在 Spring Cloud 中，我们可以基于 Spring Cloud Circuit Breaker 提供的回退机制来实现服务降级。在开发过程上，我们只需要提供一个回退方法实现并进行配置即可。这里同样也给出对应的实现方式。&lt;/p&gt;
&lt;p&gt;我们举个例子，假设系统中存在一个代表用户业务的用户服务，那么当访问这个服务时，我们就可以实现回退方法。在回退方法的实现过程中，唯一需要注意的就是该回退方法的参数和返回值必须与真实的方法完全一致。&lt;/p&gt;
&lt;p&gt;如下所示的就是回退方法的一个示例：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;91697442722817160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private UserMapper getUserFallback(String userName) {
   UserMapper fallbackUser = new UserMapper(0L,&amp;quot;no_user&amp;quot;,&amp;quot;not_existed_user&amp;quot;);
   return fallbackUser;
}`, `91697442722817160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUserFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt; fallbackUser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;no_user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;not_existed_user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fallbackUser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们可以基于 Spring Cloud Circuit Breaker 实现服务回退，开发流程也比较固化。首先，我们需要创建一个 CircuitBreaker 实例，然后实现具体的业务逻辑并提供一个回退方法，最后执行 CircuitBreaker 的 run 方法，示例代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1105388114740768500&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 创建 CircuitBreaker
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(&amp;quot;user&amp;quot;);

// 封装业务逻辑
Supplier&lt;UserMapper&gt; supplier = () -&gt; {
   return userClient.getUserByUserName(userName);
};

// 初始化回退函数
Function&lt;Throwable, UserMapper&gt; fallback = t -&gt; {
   UserMapper fallbackUser = new UserMapper(0L,&amp;quot;no_user&amp;quot;,&amp;quot;not_existed_user&amp;quot;);
   return fallbackUser;
};

// 执行业务逻辑
circuitBreaker.run(supplier, fallback);`, `1105388114740768500`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 创建 CircuitBreaker&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;CircuitBreaker&lt;/span&gt; circuitBreaker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; circuitBreakerFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 封装业务逻辑&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Supplier&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; supplier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; userClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserByUserName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 初始化回退函数&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; fallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt; fallbackUser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;no_user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;not_existed_user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fallbackUser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 执行业务逻辑&lt;/span&gt;
circuitBreaker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supplier&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fallback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述示例代码可以根据具体需求进行调整并嵌入到各种业务场景中。&lt;/p&gt;
&lt;h4 id=&quot;基于拦截器实现回退&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E%E6%8B%A6%E6%88%AA%E5%99%A8%E5%AE%9E%E7%8E%B0%E5%9B%9E%E9%80%80&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于拦截器实现回退&lt;/h4&gt;
&lt;p&gt;限于篇幅关系，我们在这里主要探讨如何实现一个降级体系的设计思路。服务降级的目标就是一旦出现异常确保能够正确回退，这让我们首先想到了可以通过引入 AOP 机制来对异常进行拦截。一旦拦截成功，那么就可以嵌入自定义的回退方法并执行该方法中的回退逻辑。整个设计思路下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-12-11-01-32-3e46af2da04cb4bab0992d6313b314c3-04139.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 414px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 58.212560386473434%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAAB30lEQVQoz41T2W7aUBD1/7/3KXml1KKhJUSVa1Bp2criBYNtYoKNTbAhbGkoLYtjTieOIKRx1Yw0Gs1dzpy5cy6T/5aA2kpBqJ9BkT9CFpNoKinUqiyMTgsPFgQBdrvdq5wR6iwWd18JKAbLPIeuJtCjOB3zaLUqB8DXGqNpEl0sQ9fqKJczkOUiVLWCRqOEyeTmALhn+WCPbIJIZ47Rb28XLyruQf6Vv2A4n88xGnmYzSbIZnlcXuoEPA3XtttteMh1XViWhdVqFebj8Q3lXThOD4OBQ96HbVuhM4VCHH2bg9J4T4OJ04CSoesaRVUIARzHRrfbxXq9Rr/vIZdjMZ3kMLjOQBLfQZJYeG42zBlJPMOMNkUhjrb2AW09ScAxDD2e3vE7sbwPgTabDXx/C00zkPvyFsF9DXc/CrC65zTMNH79LNFwi2B6PZPabKJjqOh0NBgUDaMFXW9iuVxGvtPVlU7SqtAZkRgWqRuBCgk03NrzoURZlNaObTicEPsnWTF7SUT5c5k8gZlmG5KcJ6YyOC6BfOETfQIRilICc6i4v4QjNo8bYdyfWyx+U4s8Ar8Ks5OCbV1g5GXgXnNYryr/b/lv/fl+QN+SQ7UaA//5BOn0G2Qyp+D5E9RrLP4A5lp/Mrv4CdEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 12 11 01 32&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-12-11-01-32-3e46af2da04cb4bab0992d6313b314c3-04139.png&quot; data-srcset=&quot;/static/2024-11-12-11-01-32-3e46af2da04cb4bab0992d6313b314c3-53fb7.png 200w,
/static/2024-11-12-11-01-32-3e46af2da04cb4bab0992d6313b314c3-e3470.png 400w,
/static/2024-11-12-11-01-32-3e46af2da04cb4bab0992d6313b314c3-04139.png 414w&quot; data-sizes=&quot;(max-width: 414px) 100vw, 414px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;事实上， Spring Cloud Circuit Breaker 中的 Fallback 概念和 Dubbo 中 Mock 的概念异曲同工。两者都是实现服务降级的常用技术手段。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-11&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;针对服务降级，在面试过程中，相比 Mock 机制，我认为回退机制被问题的频率会更高，是我们重点需要掌握的知识点。事实上，服务回退在分布式系统构建过程中并不能算是一个非常独立的知识点，而是属于服务降级体系下的一个具体实现策略。所以，需要面试者具有比较广的知识面，并能从概念到实现过程上对服务回退有一定的了解。&lt;/p&gt;
&lt;p&gt;事实上，服务回退的概念并不难理解。在传统开发模式下，我们在系统发生异常时通常都会返回一个默认的提示信息。服务回退与这种处理方式是类似的，本质上就是一个回调处理机制，能够针对某个方法提供缺省的返回值。在实施过程中，常见的做法是对业务方法提供一个对应的回退方法，回退方法的参数和返回值必须与真实的方法完全一致，这样确保系统获取到缺省的返回值之后还能够正常运行。&lt;/p&gt;
&lt;p&gt;关于服务降级的相关问题，还有一种开放式的提问方式，比方说 &lt;code class=&quot;language-text&quot;&gt;如果让你来实现一个服务回退机制，你会怎么做？&lt;/code&gt;。有时候，面试官很难从那些概念类的标准答案中看出不同面试者的水平差异，这时候就可以通过类似本题的方式进行考查。&lt;/p&gt;
&lt;p&gt;从考查难度而言，这种开放式的面试题对面试者而言有利有弊。一方面，这种面试题没有标准答案，面试者可以自由发挥，只要做到自圆其说就行。另一方面，这种面试题可以会让面试者感到无从下手，从而导致没有很好的回答思路。&lt;/p&gt;
&lt;p&gt;从设计思想上讲，我们可以基于拦截器来实现服务回退机制。在这套自定义的实现机制中，通过引入 AOP 对异常进行拦截。一旦拦截成功，那么就可以嵌入自定义的回退方法并执行该方法中的回退逻辑。事实上，在 Spring Cloud 等主流开源框架中，也正是基于类似的机制提供了服务回退功能。在回答这道面试题时，我们可以首先阐述自己的设计思想，然后结合开源框架给出具体的实现原理。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-9&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲是远程过程调用中所要介绍的最后一个技术组件，我们对服务降级展开了讨论。&lt;/p&gt;
&lt;p&gt;服务降级的常见实现模式包括模拟和回退两种。这两种实现模式体现的都是一种自动化的处理策略，当服务响应出现问题时能够返回一个处理结果，从而避免对目标服务执行真正的远程调用。&lt;/p&gt;
&lt;p&gt;从下一讲开始，我们将进入到与微服务架构相关技术组件的讨论。我们首先要讨论的是注册中心，而注册中心也有多种实现方式。下一讲的主题是：如何设计一款具备实时通知能力的注册中心模型？我们下一讲再聊。&lt;/p&gt;
&lt;h1 id=&quot;注册中心：如何设计一款具备实时通知能力的注册中心模型？&quot;&gt;&lt;a href=&quot;#%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%EF%BC%9A%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E4%B8%80%E6%AC%BE%E5%85%B7%E5%A4%87%E5%AE%9E%E6%97%B6%E9%80%9A%E7%9F%A5%E8%83%BD%E5%8A%9B%E7%9A%84%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E6%A8%A1%E5%9E%8B%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;注册中心：如何设计一款具备实时通知能力的注册中心模型？&lt;/h1&gt;
&lt;p&gt;通过前面几讲内容的介绍，我们已经掌握了与远程过程调用相关的各个技术组件。在分布式系统中，远程过程调用解决的就是两个服务之间的点对点调用过程。显然，想要完成这个调用过程，服务消费者首先得知道目标服务提供者的地址信息。这个地址信息是服务实例信息中的一个元数据，其他的元数据还包括服务的上线时间、可用状态等。&lt;/p&gt;
&lt;p&gt;如何对这些元数据进行有效管理是分布式系统构建过程中不得不考虑的一个问题，特别是在服务实例数量非常庞大的场景下。这就引出了本讲将要介绍的一个新的技术组件，即注册中心。&lt;/p&gt;
&lt;p&gt;注册中心的实现模型有多种，今天我们介绍其中的一种，即：如何设计一款具备实时通知能力的注册中心模型？围绕这个问题的讨论也经常出现在面试过程中，让我们一起来看一下。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-9&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，通常会存在几十个甚至上百个服务，开发人员可能甚至都无法明确系统中到底有哪些服务正在运行。另一方面，我们很难同时确保所有服务都不出现问题，也很难保证当前的服务部署方式不做调整和优化。由于自动扩容、服务重启等因素，服务实例的运行时状态也会经常变化。通常，我们把这些服务实例的运行时状态信息统称为服务的元数据（Metadata）。关于元数据这个词，我们已经在介绍服务发布流程时提到过，你可以做一些回顾。&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;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 id=&quot;问题分析-10&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;注册中心是我们引入的第一个微服务架构技术组件。为什么我们需要专门提取微服务架构技术组件，就是为了应对大规模分布式系统的构建需求。&lt;/p&gt;
&lt;p&gt;如果一个分布式系统的服务数量达到一定的规模，就需要引入专门的组件来对这些服务的元数据进行管理。而这种管理过程显然应该具备一定的机制和策略，所以我们需要设计一个模型，这就是注册中心模型。关于注册中心模型的讨论是我们应对这类面试题的第一个要点。&lt;/p&gt;
&lt;p&gt;应对这类面试题的第二个要点是深入注册中心的实现原理，其中最核心的一个问题就是：如果服务提供者的元数据发生了变化，如何实时地通知到各个服务消费者呢？这是我们要回答的第二个要点。&lt;/p&gt;
&lt;p&gt;最后，我们明确注册中心本身是一种架构设计上的模型和机制，需要依托于具体的框架才能得以实现。因此，我们需要基于具体的开源框架来对前面提出的核心问题进行分析，并阐述底层的实现原理。&lt;/p&gt;
&lt;p&gt;业界可以用来实现注册中心的框架有很多，一方面我们可以选择自己比较擅长的框架展开讨论。另一方面，我也建议你直接从 Dubbo 等主流的开源框架入手，来看看它们具体是如何设计和实现的。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-12&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在分布式系统中，我们引入注册中心的目的是实现服务的自动注册和发现机制。围绕这两个操作，我们可以先来探讨注册中心所应该具备的模型结构。&lt;/p&gt;
&lt;h3 id=&quot;注册中心模型&quot;&gt;&lt;a href=&quot;#%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E6%A8%A1%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;注册中心模型&lt;/h3&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;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-10-08-06-174fe4ff32b47785a1229471829b5e92-efa6e.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 603px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 52.57048092868989%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACE0lEQVQoz4WS2W7TUBCG/T6Fq3KLuEBCRSD1hneAC3gMKlqJV0BcVaUVAlWBbkASaJFKoEmTKl7ipdlMFZLYWUgcm+Tj2ElKFFVi5N9zPGf+8WwSExmNRhFC6fZ8zI9PqB3cwYrdx9i+i72/TCV2C+Pr88hnOAwuOVNeKNKs8V/AgIvDx3iHt6nsLFF6v0Qzfo9O/Cbl47UJdcg8N4QkchPPcAxx7vd7tNstXKeBXpC5sEviXKNcMjF0lX6vi+f1cMT9PDcKOBqbmSYd/IF218MPuFIGPvwWFfS94bhVc3zJKnxCl3cxlL0I6tk7lFwMU9grFXna4ehdrcqRXcnG0PO7Y56Ant8hjKNpRyLg7jLFresoG4sU39xAeyX01gJ64iFWKYXXHxAEPp12F1U/wvr8iPLrBfLri5Qm/tbmNcy9B8jKNpJTfEE584zC8VP0byFWsHNrOJV1kWGKZtPFskxqtTqG8YXWzw2qp6vCfwUztRJxqtlVnOJLLHMHqdM9o9bIiL8nyatxNKEbbo5G8zul4slkRcb9KpfTNN0fkb+sJVC0JEohSa2eodXOoioHSKqioqm6gIEsa2KyFudWUUzRxZ9MZrpOAzERx2nRcluiBZ0xOp3o2xX+vZ6HFDrZtiECpUifJDg/T1P/ZTO78LP6fyL5/lBMLy3K2+c0/ZZq5QN2VSGscj7YVYsctiMIgkv8BUnkIvUB/esSAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 10 08 06&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-10-08-06-174fe4ff32b47785a1229471829b5e92-efa6e.png&quot; data-srcset=&quot;/static/2024-11-13-10-08-06-174fe4ff32b47785a1229471829b5e92-b4d75.png 200w,
/static/2024-11-13-10-08-06-174fe4ff32b47785a1229471829b5e92-f2144.png 400w,
/static/2024-11-13-10-08-06-174fe4ff32b47785a1229471829b5e92-efa6e.png 603w&quot; data-sizes=&quot;(max-width: 603px) 100vw, 603px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，基本的工作流程通过操作语义即可理解。但有一个问题需要解决，即一旦服务的运行时状态发生了变更，我们如何有效获取这些变更信息呢？这就需要在注册中心中进一步引入变更通知机制，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-10-08-48-81107f40cdb4ffe04d3ffbd613d40e54-3e989.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 605px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 52.396694214876035%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACGElEQVQoz1VSTW/aQBD172qlNOmpbQ5V0t6i/qAoH8eqUjmk6qU95YukQGhpkqpqlDgqxijYUMCAMbHBBgPxB9ivswYUWGk0u7Nv3pvdGQ7TFYYh5le7KUNNrkJLraJ09BKV+CvoZy9QT2/AsqwF7Hwuxw6zwHg8xmDQj/YVmYd2tgY98xbS4WtUTtdhnq+hmnqHdrsdYYbDQZQzI2XGTTWmhCN0uxYc5yECu65Hfgjfd6M713Vh23145F3CWJYZxec5OM/z4ZOIHxAhxR68AK4fRnvH8WCaJnTdgDGtajQOJneEc6Y4lss4HMcBJ0sZ5K8+QLjYgnC5g+z5Nm7Sm+C/b+JO3CciFT4T9X2qSEdJOoH4axe5y138/blFfge5i22If95DVX+Dq1aS0DJvoB4/QS2+jObpCtky7hNLaEgxtPQa7J5NT7UhySLU4idY6SVUD59BSzxH89sKtJOn0H6s05fw4BQlib6ZQKP8FeJtDDn+I8p3n9FW99ExUvTcKjodk/62B0WRYHczdHeAgrAHkY+hkN1DvfQFndYxveSKEQrQNFJWRRRLN5Dla0rMwjCkKN63H0dk0O+hpeXpX0toNEQoNQH1eo5EJRItQmuK4IZDl8juUVNUIvuHQkFGuaxQUg+z8ZofrYAaMBqFkWehkDWTGsKazc7cBBRQVdfIi3Hc8geolDPUAGNCxjIXBnjRHsWYD/Af9HvZaVEeP+UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 10 08 48&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-10-08-48-81107f40cdb4ffe04d3ffbd613d40e54-3e989.png&quot; data-srcset=&quot;/static/2024-11-13-10-08-48-81107f40cdb4ffe04d3ffbd613d40e54-4bd12.png 200w,
/static/2024-11-13-10-08-48-81107f40cdb4ffe04d3ffbd613d40e54-7acd9.png 400w,
/static/2024-11-13-10-08-48-81107f40cdb4ffe04d3ffbd613d40e54-3e989.png 605w&quot; data-sizes=&quot;(max-width: 605px) 100vw, 605px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从设计理念上讲，我们希望这种来自注册中心的变更通知能够实时地同步到服务消费者，这时候就可以引入推送思想。那么，如何具体实现推送呢？我们可以采用监听机制。所谓监听机制，指的就是服务消费者对位于注册中心的元数据添加监听器，一旦元数据发生变化，就可以触发监听器中的回调函数。&lt;/p&gt;
&lt;p&gt;我们可以在回调函数中对已变更的元数据执行任何操作，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-10-09-24-b4ea07ca06d5f84e094962f7a0c2d28d-73362.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 609px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.14121510673235%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABpUlEQVQoz4WS6W7TUBBG/f7vwA9ALIoQokGACqqEQDR70ya2E+LUNRGJ42yO4yzeD2O3VBVEMNKn0ZU93505d5Th8JyefoYx+Mx0+pPB9ybdzmt0tYyuvaXfO2EwqHI4RERRSJZl5JHnY1Icx8CeaDhOj9Vyga5XqdeeiV7SbJRoNV8wNOokSUYU56YR2Z3hMWOFPyJJEjYb/17rtcdutyOOY1L5lqXp/b+/TR+GEgRbwnBLEPjF7a7rMJsZzOfXLOam5CGeNyOvncoElm0zWS7x5ZKZ4wiKvdSJwlwHFF075eryDWq3zA/LoNP5Qr36iEbtsYz8lMv2Ey7aH1k4CypTm/JoxHsxbZomG3ctqEbC+R3dqxOp/4ay222lAxff92TcVB7GkgeqiGoFO/O6Jl2OinFm7gpzPL7tcL+/QxSz8daCZkXu9RfDPKIoE2a3CsJUzknBMBHxgNtRhrbdZzJWsW1N+C25uVHR1A+ySp/o988kn8ooXdJULpBu/vvKltWQ8b5iWefCYyxrU+Gi9Zx2qyT8XqF2SgWCIIiLPcy3gH8Y/gIcwqPudF11uAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 10 09 24&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-10-09-24-b4ea07ca06d5f84e094962f7a0c2d28d-73362.png&quot; data-srcset=&quot;/static/2024-11-13-10-09-24-b4ea07ca06d5f84e094962f7a0c2d28d-06e0b.png 200w,
/static/2024-11-13-10-09-24-b4ea07ca06d5f84e094962f7a0c2d28d-70531.png 400w,
/static/2024-11-13-10-09-24-b4ea07ca06d5f84e094962f7a0c2d28d-73362.png 609w&quot; data-sizes=&quot;(max-width: 609px) 100vw, 609px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，服务消费者可以对具体的服务实例节点添加监听器，当这些节点发生变化时，注册中心就能触发监听器中的回调函数确保更新通知到每一个服务消费者。显然，使用监听和通知机制具备实时的数据同步效果。&lt;/p&gt;
&lt;h3 id=&quot;注册中心实现工具&quot;&gt;&lt;a href=&quot;#%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E5%AE%9E%E7%8E%B0%E5%B7%A5%E5%85%B7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;注册中心实现工具&lt;/h3&gt;
&lt;p&gt;以上关于注册中心的讨论为我们提供了理论基础。根据这些理论基础，业界也诞生了很多具体的实现工具，常见的包括 Consul 、Zookeeper、Eureka 和 Nacos 等。我们无意对这些工具做一一展开。在本讲中，我们将基于 Zookeeper 来具体分析注册中心的实现模型。Zookeeper 是基于监听和通知机制的典型框架。&lt;/p&gt;
&lt;p&gt;从物理结构上讲，Zookeeper 就是一个目录树，包含了一组被称为 ZNode 的节点，它的基本结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-10-10-11-f004736745103b97175701e54327d3ea-3e989.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 605px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 39.83471074380166%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABhklEQVQoz2VS2U7DQAzM/38JvCEeigRShRCoVIGmJYI2R3Mn3UKT3leuYbJ9KKgrWYntHc/YXgWo8d/O53jMYdvPCPwH+N4DRsN7xg64PGe80rhlCVTVKVUUNbbbI9brPbJsjjj2ISYhgmDMf0/GVqsdNpuDvFtVJxENvjHFc1UksUoVr3BdG76v8/sCY9RGkjhYLIRUaZlPLBZIAsN4pOIOLKtLP4Iz7iAKVUySHhQhhoijPsJQYzEXUWQw2SPJgCoyqvmBEDpmsy+kaYD1aoHp9FNi0tTFfr8jtkdfw/e3DiVNI5imRtAYy+WCRU24jk5VAwJjxiMy2ySwWHwmC8SxKTFZllxMU2la0T9alP1IxinBQ7yrt/gYtOiHbNvC+9sNtN4tC30inQmYRpv5O7atcv6VXEZdV9KUsqzlMIuiCYBtruC5FgsFOBz254Ez1+SLIseES3Ickx3NLxX+dfK8ZMtd9LVraNoVgaPTo5Dsp20K4WFst0h6x8Xd80WsSViRqJD2C5T/VyY4RWxTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 10 10 11&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-10-10-11-f004736745103b97175701e54327d3ea-3e989.png&quot; data-srcset=&quot;/static/2024-11-13-10-10-11-f004736745103b97175701e54327d3ea-4bd12.png 200w,
/static/2024-11-13-10-10-11-f004736745103b97175701e54327d3ea-7acd9.png 400w,
/static/2024-11-13-10-10-11-f004736745103b97175701e54327d3ea-3e989.png 605w&quot; data-sizes=&quot;(max-width: 605px) 100vw, 605px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，size 节点位于 &lt;code class=&quot;language-text&quot;&gt;/business/product/size&lt;/code&gt; 路径，节点 C 可以存储数据 350，而节点 &lt;code class=&quot;language-text&quot;&gt;/D/order/1&lt;/code&gt; 可能存储着类似 &lt;code class=&quot;language-text&quot;&gt;{&amp;quot;id&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;itemName&amp;quot;:&amp;quot;Notebook&amp;quot;,&amp;quot;price&amp;quot;:&amp;quot;4000&amp;quot;,createTime=&amp;quot;2022-06-16 22:39:15&amp;quot;}&lt;/code&gt; 等复杂数据结构和信息。Zookeeper 中所有数据通过 ZNode 的路径被引用。&lt;/p&gt;
&lt;p&gt;Zookeeper 特性很多，我们可以从注册中心的基本实现需求出发，结合模型及其操作来把握用于构建注册中心的相关技术。&lt;/p&gt;
&lt;p&gt;首先，Zookeeper 专门设计并实现了一个监听器组件。我们可以在任何一个 ZNode 上添加监听器，并实现对应的回调函数，从而确保服务器端的变化能够通过回调机制通知到客户端。&lt;/p&gt;
&lt;p&gt;另一方面，Zookeeper 中也提供了临时节点的概念。所谓临时节点，指的是只要客户端与 Zookeeper 的连接发生中断，那么这个节点就会自动消失。显然，临时节点的这种特性可以用于控制该节点所包含的服务定义元数据的时效性。&lt;/p&gt;
&lt;p&gt;ZNode 是 Zookeeper 中可以用代码进行控制的主要实体。对 ZNode 的基本操作包括节点创建 create、删除 delete、获取子节点 getChildren 以及获取和设置节点数据的 getData/setData 方法。操作 Zookeeper 的客户端组件包括自带的 ZooKeeper API 和第三方 zkClient、Curator 等，这些客户端都对 Zookeeper 连接资源管理和对 ZNode 节点的各项操作做了不同程度的封装。&lt;/p&gt;
&lt;p&gt;Zookeeper 中涉及的主要操作如下表所示，在源码解读过程中，我们会发现对 Zookeeper 的控制基本都是对这些操作的封装和应用。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;操作&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;create&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;在 ZooKeeper 命名空间的指定路径中创建一个 ZNode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;delete&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;从 ZooKeeper 命名空间的指定路径中删除一个 ZNode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;exists&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;检查路径中是否存在 ZNode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;getChildren&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;获取 ZNode 的子节点列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;getData&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;获取与 ZNode 相关的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;setData&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;将数据设置/写入 ZNode 的数据字段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;getACL&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;获取 ZNode 的访问控制列表（ACL）策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;setACL&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;在 ZNode 中设置访问控制列表（ACL）策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;sync&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;将客户端的 ZNode 视图与 ZooKeeper 同步&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;介绍完注册中心模型以及 Zookeeper 框架，让我们回到 Dubbo。作为一款主流的分布式服务框架，Dubbo 也内置了一整完整的注册中心实现方案，默认采用的就是 Zookeeper。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-10&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在 Dubbo 内部，提供了 Multicas、Zookeeper、Redis、Nacos、Consul、Etcd3 等一大批注册中心实现方式。我们先从它的注册中心模型开始讲起。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-注册中心模型&quot;&gt;&lt;a href=&quot;#dubbo-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E6%A8%A1%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 注册中心模型&lt;/h3&gt;
&lt;p&gt;Dubbo 中的注册中心代码位于 dubbo-registry 工程中，其中包含了一个 dubbo-registry-api 工程，该工程包含了 Dubbo 注册中心的抽象 API，而剩下的 dubbo-registry-default、dubbo-registry-zookeeper、dubbo-registry-nacos 等工程则是这些 API 的具体实现，分别对应前面提到的各种注册中心实现方式。我们同样无意对所有这些注册中心实现方式做详细展开，而是重点关注抽象 API 以及基于 Zookeeper 的实现方式。&lt;/p&gt;
&lt;p&gt;我们首先来看一下 dubbo-registry-api 工程，这里面最核心的就是如下所示的 RegistryService 接口：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;38863072251832760000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface RegistryService {
   // 注册
   void register(URL url);
   // 取消注册
   void unregister(URL url);
   // 订阅
   void subscribe(URL url, NotifyListener listener);
   // 取消订阅
   void unsubscribe(URL url, NotifyListener listener);
   // 根据 URL 查询对应的注册信息
   List&lt;URL&gt; lookup(URL url);
}`, `38863072251832760000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegistryService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 注册&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 取消注册&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unregister&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 订阅&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotifyListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 取消订阅&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotifyListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 根据 URL 查询对应的注册信息&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;URL&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，RegistryService 所有操作的对象都是 URL，而订阅相关的操作中还附加了监听器 NotifyListener，确保变更信息的推送。从命名上我们已经可以初步猜想 Dubbo 在注册信息变更时采用的就是监听和通知机制。通过确认 NotifyListener 接口的定义更加明确了我们的猜想，因为该接口中只有一个 notify 方法，用于将发生变更的注册信息以 URL 的形式进行通知，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;44788444926982414000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface NotifyListener {
   void notify(List&lt;URL&gt; urls);
}`, `44788444926982414000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotifyListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;URL&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; urls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们再来看 RegistryFactory 接口，如下所示。这里的 &lt;code class=&quot;language-text&quot;&gt;@SPI(&amp;quot;dubbo&amp;quot;)&lt;/code&gt; 注解我们会在后面介绍微内核模式时进行介绍，代表默认情况下使用 Dubbo 自身的注册中心。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;59171588015164180000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;dubbo&amp;quot;)
public interface RegistryFactory {
   Registry getRegistry(URL url);
}`, `59171588015164180000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dubbo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegistryFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Registry&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从接口的命名上可以看出 RegistryFactory 是 Dubbo 中创建注册中心的工厂类，通过对 RegistryFactory 的实现，Dubbo 提供了 Zookeeper、Redis 等几种不同的注册中心实现方案。&lt;/p&gt;
&lt;p&gt;可以说 Dubbo 中关于注册中心 API 层的抽象简单而清晰，比较适合先用来做对全局代码结构的把握。在这层 API 抽象之下，我们重点介绍 ZookeeperRegistry 和 ZookeeperRegistryFactory。&lt;/p&gt;
&lt;h3 id=&quot;zookeeper-注册中心实现过程&quot;&gt;&lt;a href=&quot;#zookeeper-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E5%AE%9E%E7%8E%B0%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Zookeeper 注册中心实现过程&lt;/h3&gt;
&lt;p&gt;让我们来到 Dubbo 源码，来看一下 ZookeeperRegistry 的实现过程，而 ZookeeperRegistry 中最重要的就是它的构造函数，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19043088783814580000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
   // ...
   // 建立与 Zookeeper 的连接
   zkClient = zookeeperTransporter.connect(url);
   // 添加状态监听器
   zkClient.addStateListener(new StateListener() {
      public void stateChanged(int state) {
         if (state == RECONNECTED) {
            try {
               recover();
            } catch (Exception e) {
               logger.error(e.getMessage(), e);
            }
         }
      }
   });
}`, `19043088783814580000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperTransporter&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 建立与 Zookeeper 的连接&lt;/span&gt;
   zkClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 添加状态监听器&lt;/span&gt;
   zkClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addStateListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StateListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stateChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; RECONNECTED&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;recover&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里执行了两个操作，一个是与 Zookeeper 建立连接，另一个就是添加了用于断线重连的状态监听器。根据对 Zookeeper 基本操作的了解和掌握，上述实现过程都是使用 Zookeeper 时的常规步骤。&lt;/p&gt;
&lt;p&gt;为了理解这段代码，我们需要明确另外两个核心对象的创建过程，这两个核心对象分别是 ZookeeperTransporter 和 ZookeeperClient。我们发现 ZookeeperTransporter 是在 ZookeeperRegistryFactory 工厂类创建 ZookeeperRegistry 时带进来的，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;48904751395507250000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
   private ZookeeperTransporter zookeeperTransporter;

   public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
      this.zookeeperTransporter = zookeeperTransporter;
   }

   public Registry createRegistry(URL url) {
      return new ZookeeperRegistry(url, zookeeperTransporter);
   }
}`, `48904751395507250000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperRegistryFactory&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractRegistryFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperTransporter&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setZookeeperTransporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ZookeeperTransporter&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;zookeeperTransporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Registry&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; zookeeperTransporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ZookeeperTransporter 本身是一个接口，定义也比较简单，就是根据传入的 URL 创建与 Zookeeper 服务器的连接并获取一个 ZookeeperClient 对象，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;73618776203083235000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;zkclient&amp;quot;)
public interface ZookeeperTransporter {
   @Adaptive({ Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY })
   ZookeeperClient connect(URL url);
}`, `73618776203083235000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;zkclient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperTransporter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLIENT_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TRANSPORTER_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ZookeeperClient&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;另一方面，在 ZookeeperClient 接口的定义中包含了注册中心运行过程中所有的数据操作，如创建和删除路径、获取子节点、添加和删除 Listener、获取 URL 等实现发布订阅模式的入口。这些方法名与 Zookeeper 原生操作基本一致，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;6819888415503450000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ZookeeperClient {
   void create(String path, boolean ephemeral);
   void delete(String path);
   List&lt;String&gt; getChildren(String path);
   List&lt;String&gt; addChildListener(String path, ChildListener listener);
   void removeChildListener(String path, ChildListener listener);
   void addStateListener(StateListener listener);
   void removeStateListener(StateListener listener);
   boolean isConnected();
   void close();
   URL getUrl();
}`, `6819888415503450000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperClient&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; ephemeral&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getChildren&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addChildListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeChildListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addStateListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StateListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeStateListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StateListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isConnected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;目前可以与 Zookeeper 服务器进行交互的客户端有很多，Dubbo 中提供了对 Zkclient 和 Curator 这两个客户端工具的集成，对应的 Transporter 和 ZookeeperClient 实现类见下图。Dubbo 使用 Zkclient 作为其默认实现。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-11-14-06-2363ab524e92ddda83af9677cc604ece-e9054.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 585px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 88.03418803418805%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAAC0klEQVQ4y4VUaVMaQRTk//+CVBlNPFBirCQGjNEP8QCXa3c5FkGO5VgQ5RCEBcVwdHpn5UoRM1Wv5qCn971+PTjw15hMxmLO5WSkb9yQwwcIBfeRSR8hr1/CNHuvuMnKcMyJpoc2YT4fQjKxj4jiYnxCPOaCUTpHtzsnXDWWCK0xHtuEyWuJ2e0hoh6Q8ABR9TNU9QhPT4MF/BsZLpIOh2Nm0md5fdRqDZRKZbHudEwMBsOliv6b4Wg0QbEYo34XyGUlpJIXyGa8XF9RRy/XEvr9f5ftWNZuIjJIaD+hKh8YO4xtKPIWlPAWtJiT8y5areZMnsVylzJcHJWKjnSaXU6HYRgJEfl8FMlkAHpOw8vL75UNERre3xmo18uzeGjekrCAQj5DzToz8HA44bmBUjGLeq2MRr2CZqMi5lrNYNYNO0NVdiEU2EY45GR5e6KkIPfx2C6bEcNr09Fut6nrEbvuhByiFPIuXeBEPLrPu5vU99Im1OIeCn2CbPoEWvwQN6lj7k/ZDA+NrM0y7HS6uE6cEncssAnNTWt5kCM2ef2d9ySb0Ow+EdxHz3xmmVbqXVhnj489UeZiN3u9ATo8t2bDqKJ23xRrC/v8bNvJoecU6LpCMpWCy8xKRaEQEeVmMyF+rCWApdINf5OJi4iwcNbztLDFYhRFztmsBkdAWofvcg2S7z183jWEgx8RkDZE+K/WUL3NCUJFcRPzDn5xvo4r4gP+Der9QdyNqjs4++WCo1QM8mv+15DEbBm4bIRpGy/arbog1PUYOxxgJiFh8vkdO/Scnw3zrfah1dm7u7rQyOya1OiRWvVpm3tUqzW8NRzTVzINa5hmh9mco/UgiS4mNA/07Al95+P52UxX+6UshwPTZ7PwfCzChPYV6dQXarmJoJ9Pj76z9pm0e0b45p/DIqDb7dCs33CT/EE/HgufWv5Mca8qh5RgnqEVo9Fotv4D/LIzd/MTeK0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 11 14 06&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-11-14-06-2363ab524e92ddda83af9677cc604ece-e9054.png&quot; data-srcset=&quot;/static/2024-11-13-11-14-06-2363ab524e92ddda83af9677cc604ece-5fafc.png 200w,
/static/2024-11-13-11-14-06-2363ab524e92ddda83af9677cc604ece-0bfa6.png 400w,
/static/2024-11-13-11-14-06-2363ab524e92ddda83af9677cc604ece-e9054.png 585w&quot; data-sizes=&quot;(max-width: 585px) 100vw, 585px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;接下来终于到了分析注册中心具体操作的时候了，ZookeeperRegistry 提供了 doRegister、doUnregister、doSubscribe 和 doUnsubscribe 方法分别对应注册/取消注册、订阅/取消订阅这四个具体操作。&lt;/p&gt;
&lt;p&gt;我们首先来看一下注册方法 doRegister，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;77953186000221140000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected void doRegister(URL url) {
   try {
      zkClient.create(toUrlPath(url),
      url.getParameter(Constants.DYNAMIC_KEY, true));
   } catch (Throwable e) {
      // ...
   }
}`, `77953186000221140000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doRegister&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      zkClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUrlPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DYNAMIC_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不难看出，注册操作的实现方式就是在 Zookeeper 中创建一个节点。请注意，默认创建的节点都是临时节点，当连接断开之后会自动删除。对应的，我们也不难想象取消注册的实现方式就是删除这个临时节点，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70354101025376270000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected void doUnregister(URL url) {
   try {
      zkClient.delete(toUrlPath(url));
   } catch (Throwable e) {
      // ...
   }
}`, `70354101025376270000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doUnregister&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      zkClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUrlPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们再来看订阅过程。在订阅 URL 过程中，Dubbo 将传入的回调接口 NotifyListener 转换成 Zookeeper 中的 ChildListener，并主动根据服务提供者 URL 调用 NotifyListener。&lt;/p&gt;
&lt;p&gt;doSubscribe 方法比较长，我们提取其中的核心代码，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70942876791405080000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
   // 添加子节点监听器
   listeners.putIfAbsent(listener, new ChildListener() {
      public void childChanged(String parentPath, List&lt;String&gt;  currentChilds) {
         for (String child : currentChilds) {
            child = URL.decode(child);
            if (!anyServices.contains(child)) {
               anyServices.add(child);
               subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, Constants.CHECK_KEY, String.valueOf(false)), listener);
            }
         }
      }
   });
   zkListener = listeners.get(listener);
}`, `70942876791405080000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt; zkListener &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; listeners&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zkListener &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 添加子节点监听器&lt;/span&gt;
   listeners&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listener&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;childChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; parentPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;  currentChilds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; child &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentChilds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            child &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; URL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;anyServices&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               anyServices&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;INTERFACE_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; child&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CHECK_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   zkListener &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; listeners&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，Dubbo 会订阅父级目录, 而当有子节点发生变化时就会触发 ChildListener 中的回调函数，该回调函数会对该路径下的所有子节点执行 subscribe 操作。&lt;/p&gt;
&lt;p&gt;而取消订阅 URL 的过程实际上只是去掉 URL 上已经注册的监听器，doUnsubscribe 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69318244860570750000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected void doUnsubscribe(URL url, NotifyListener listener) {
   ConcurrentMap&lt;NotifyListener, ChildListener&gt; listeners = zkListeners.get(url);
   if (listeners != null) {
      ChildListener zkListener = listeners.get(listener);
      if (zkListener != null) {
         // 取消子节点监听器
         zkClient.removeChildListener(toUrlPath(url), zkListener);
      }
   }
}`, `69318244860570750000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doUnsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotifyListener&lt;/span&gt; listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ConcurrentMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NotifyListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; listeners &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zkListeners&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listeners &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;ChildListener&lt;/span&gt; zkListener &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; listeners&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zkListener &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 取消子节点监听器&lt;/span&gt;
         zkClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeChildListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUrlPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; zkListener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;到此为止，ZookeeperRegistry 类中的构造函数和核心方法已经分析完毕。你看到这里可能会好奇，doRegister、doUnregister、doSubscribe 和 doUnsubscribe 这四个方法是在哪里被调用的呢？毕竟 ZookeeperRegistry 本来应该实现的是 RegistryService 接口中的 register、unregister、subscribe 和 unsubscribe 方法才对。&lt;/p&gt;
&lt;p&gt;通过阅读代码，我们发现 ZookeeperRegistry 并不是 RegistryService 的直接实现类，从类层结构上，ZookeeperRegistry 扩展了 FailbackRegistry，而 FailbackRegistry 又扩展了 AbstractRegistry，注意 FailbackRegistry 和 AbstractRegistry 都是抽象类。而前面提到的这些方法在 RegistryService 不同层级的实现类中被调用，这里面涉及到的类层结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-13-11-18-20-51c590fdd7d32310c9ba0e0de8076037-ad3a8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 396px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 74.74747474747474%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACWklEQVQ4y41Ti1LaUBTM//9AHbXKtNUiSG1xVOT9jhAxCRDAEEC0kUcr0hFQq2S7CcXa2hk5MztncnPu3nPu7hUkKQpZ9iGf20Ym7UaWyB15US75mT+h1+vBDsuaEng1BKMu4uZHEoa+C+XEQ2yhWtnBZCRi8D2NS/NyRjglIRYgLBXjEDMuHOy/QTrpQjy6ipy4gZOCG4r8p8PpdMEOc7kAGsYO2mcHxD4uzgM4a+7BvAiQ1AvTNJ8IFwmh3TZQqRyj0SjxLjNIJA5gGEXouoxaTcF4PP59h5aDWacvMV8XnrPbIw2H4xenzoqtxTpUFZEjy+zqxMmnNcnJzaZCpbO4uvr2VGya5xQsj2ZD5UQK97COudVUoZ/aU9YgiNkNZFJrtIwLaebQ4RJSibdIxFZR1bzQNAk3NyPc3f1ERRNRkN4hFFxCNmMLuIJoZBlx1ko5F//HIYSDq7TMZ5SLXlTK29AIVXaTzEdR3vNOA7i+HuL+/oFejbBrNzf6nDpD96OobqGkelBUNunbfQippJ9jxlkUdVC1UYlxLQEpv0sftp9G1vUSD9lz/mvlCKfy8YAwatU4VCVAm2Vnojw+WgReYO6UucL/Rr3ewu3tw9+i2FLDeQP/x5xoOrXQ73coUgeDqy7d0KdIZ1wzMbzuYzDootv5ahNaeA12jMe3EEUfUklbsDUkE+uI8VWFQyuIhJe5vo78kWdG6HTj7Jt39tybs+/RaILC8RdoJQ+V3qSqH5wsFz46T9cW5jhPQixIOJnc4Uj003eHMOpBejXIfOigwe9WK0QrbeMX6oJMCSG86K8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 13 11 18 20&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-13-11-18-20-51c590fdd7d32310c9ba0e0de8076037-ad3a8.png&quot; data-srcset=&quot;/static/2024-11-13-11-18-20-51c590fdd7d32310c9ba0e0de8076037-2a85e.png 200w,
/static/2024-11-13-11-18-20-51c590fdd7d32310c9ba0e0de8076037-ad3a8.png 396w&quot; data-sizes=&quot;(max-width: 396px) 100vw, 396px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们继续往下看，发现真正调用 doRegister、doUnregister、doSubscribe 和 doUnsubscribe 这四个方法的地方分别是在 FailbackRegistry 对应的 register、unregister、subscribe 和 unsubscribe 方法中，这点自然比较好理解。但我们发现这四个方法还同时出现在 FailbackRegistry 的 retry 方法中。事实上，在 FailbackRegistry 构造函数中会创建一个定时任务，每隔一段时间执行该 retry 方法。&lt;/p&gt;
&lt;p&gt;在这个 retry 方法，以注册场景为例（其他场景也类似），我们从注册失败的集合中获取 URL，然后对每个 URL 执行 doRegister 操作从而实现重新注册，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;89496863402047640000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`if (!failedRegistered.isEmpty()) {
   Set&lt;URL&gt; failed = new HashSet&lt;URL&gt;(failedRegistered);
   if (failed.size() &gt; 0) {
      try {
         for (URL url : failed) {
            try {
               // 重新注册
               doRegister(url);
               failedRegistered.remove(url);
            } catch (Throwable t) {
               // ...
            }
         }
      } catch (Throwable t) {
         // ...
      }
   }
}`, `89496863402047640000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;failedRegistered&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;URL&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; failed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;URL&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failedRegistered&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;URL url &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 重新注册&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;doRegister&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               failedRegistered&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 RegistryService 还有最后一个 lookup 方法，其作用是根据 URL 查询对应的注册信息。基于 Zookeeper，这个方法的实现也比较简单，我们只需要通过 Zookeeper 提供的 getChildren 方法获取某个 ZNode 的子节点即可，这里不做展开，你可以参加 Dubbo 源码进行学习。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-12&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于注册中心的基本模型是回答任何与这一主题相关面试题的基本要点。这部分内容属于理论知识，内容也比较固定。我们可以从注册中心的诞生背景、作用、组成结构等方面进行展开，并重点提及数据变更通知特性，这一特性体现了不同注册中心之间在设计理念和运行机制上的差别。&lt;/p&gt;
&lt;p&gt;然后，关于注册中心的实现，我们需要结合具体场景和诉求来讨论。本讲中我们讨论的是 &lt;code class=&quot;language-text&quot;&gt;具备实时通知能力的注册中心模型&lt;/code&gt;，因此关注的是那些具有实时数据监听和推送功能的开源框架，例如 Zookeeper。Zookeeper 是一款非常经典的分布式协调框架，也是 Dubbo 框架的默认注册中心实现工具，所以在讨论过程中实际上也需要对 Dubbo 框架有足够的掌握。&lt;/p&gt;
&lt;p&gt;从注册中心的实现类型而言，Zookeeper 是基于推送机制完成注册信息变更通知的代表性框架。Zookeeper 能够做到这一点是因为它内置了功能强大的监听器。当 Zookeeper 客户端通过会话机制与服务器建立连接并维持心跳检测之后，任何对 Zookeeper 节点的新增、更新和删除操作都会触发监听器上的回调函数，从而完成服务定义的动态更新。这是基于 Zookeeper 实现一款注册中心的核心流程。当然，我们在回答这道题时，也可以从 Zookeeper 中对服务注册信息的定义、存储等方面做进一步展开。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-10&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;作为总结，我们明确注册中心就是这样一种服务治理工具：管理系统中所有服务实例的运行时状态，并能够把这些状态的变化同步到各个服务中。注册中心的实现有不同的策略，业界也诞生了一批不同类型的注册中心实现工具。本讲所阐述的 Zookeeper 是其中的代表性框架之一，具备实时通知能力。&lt;/p&gt;
&lt;p&gt;请注意，并不是所有的注册中心都采用的是和 Zookeeper 一样的监听和推送机制，我们也可以采用定时更新策略来获取注册中心中最新的元数据。那么，如果采用定时更新策略来设计注册中心，这个过程有哪些注意点呢？这就是下一讲要讨论的内容。&lt;/p&gt;
&lt;h1 id=&quot;注册中心：如果采用定时更新策略来设计注册中心，有哪些注意点？&quot;&gt;&lt;a href=&quot;#%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%EF%BC%9A%E5%A6%82%E6%9E%9C%E9%87%87%E7%94%A8%E5%AE%9A%E6%97%B6%E6%9B%B4%E6%96%B0%E7%AD%96%E7%95%A5%E6%9D%A5%E8%AE%BE%E8%AE%A1%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%EF%BC%8C%E6%9C%89%E5%93%AA%E4%BA%9B%E6%B3%A8%E6%84%8F%E7%82%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;注册中心：如果采用定时更新策略来设计注册中心，有哪些注意点？&lt;/h1&gt;
&lt;p&gt;上一讲我们进入到了注册中心这一技术组件的讨论，我们给出了注册中心的基本模型，并基于 Zookeeper 框架分析了 &lt;code class=&quot;language-text&quot;&gt;具备实时通知能力的注册中心&lt;/code&gt; 的实现方式。&lt;/p&gt;
&lt;p&gt;本讲继续讨论注册中心，并关注另一种实现方式，即采用定时更新策略的注册中心。可以说，这两种注册中心的设计思想和实现方式完全不同。&lt;/p&gt;
&lt;p&gt;那么，如果我们采用的是定时更新策略，应该如何获取最新的服务实例元数据呢？本讲内容将围绕这个话题进行展开。在日常开发过程中，关于定时更新策略的应用场景实际上非常多，也是面试过程中经常会出现的考查点。因此，本讲内容中所介绍的实现过程中也有很多值得深入分析和学习的开发和面试技巧。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-10&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-10&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;与注册中心相关的问题背景，我们在上一讲中已经介绍了很多，这里不再重复展开。在本讲中，因为我们是围绕 &lt;code class=&quot;language-text&quot;&gt;如果采用定时更新策略来设计注册中心&lt;/code&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;h2 id=&quot;问题分析-11&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&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;h2 id=&quot;技术体系-13&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;如果我们采用定时更新策略，那么从架构设计上讲，比较容易想到的方式是采用轮询机制。轮询机制是一种主动拉取策略，即服务的消费者定期调用注册中心提供的元数据获取接口获取最新的服务实例列表并更新本地缓存，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-14-11-49-11-aaf03d4f17d22638ad5a115a9b39db34-3e989.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 605px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.44628099173554%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABtklEQVQoz21Sa2/aQBD0//8N/dBIfVCpKorUVFXaNF8SWh7G4RFsaMGYgCGAiW0wZ3yHJ2OnQU3FSiP7du9md2dXu59ZWMwzmFitlvjdM9C4+YhW6xRt4rZdxGBQQRQJYoNnS9P0gOdzZlq//xOdziX6f64wn09hWTVUym+I96hWCtBr75ikBClTbLdbxHH8guBf4gxa5s+w3z9lllLC94McQRCw6of8K4SAkgpyJ19U+b9pSRKTRCBJtjlZ4C+xXPThefZfDBCGc8YUnMkIzV4LE89FsAkY8yCV5FtxgGaZFzCMT2g2ihgOu9TuGuVfr1Atn6BWOUFdfw3TvMRoMMY33j11vuBsco6aU8fy3oNt9xj/DKNehDO8gCZEhHXoY8OMSinMZg51vIJlltC1Suh1r6mtnbdzN72D3tYxXoxZYZj7kiRhBz78hxXW6wAajhjvsMUn7HYpz4oDiRCLGIr/Bw1xRMOp24E7MeC6BjWZwXEsrspXmJ3vrPAHKz3n2hhQ+5QJEkQkPgwkPTLl0aiRt2XbZbbmkqDKVXkLvVqgfh/QvCmQsExJBFsKuQ37o2uT+TM8AliMoZNFnHulAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 14 11 49 11&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-14-11-49-11-aaf03d4f17d22638ad5a115a9b39db34-3e989.png&quot; data-srcset=&quot;/static/2024-11-14-11-49-11-aaf03d4f17d22638ad5a115a9b39db34-4bd12.png 200w,
/static/2024-11-14-11-49-11-aaf03d4f17d22638ad5a115a9b39db34-7acd9.png 400w,
/static/2024-11-14-11-49-11-aaf03d4f17d22638ad5a115a9b39db34-3e989.png 605w&quot; data-sizes=&quot;(max-width: 605px) 100vw, 605px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们看到轮询机制实现上就是一个定时器，需要考虑定时的频率以确保数据同步的时效性。&lt;/p&gt;
&lt;p&gt;针对上图中的执行流程，我们要引出的一款注册中心实现工具是 Netflix 的 Eureka。Eureka 的基本架构由 Eureka 服务器、服务提供者和服务消费者这 3 个逻辑角色所组成，并与上一讲中介绍的服务注册中心模型具有高度一致性。虽然 Eureka 2.0 经历了诸多变故之后目前处于停止更新状态，但作为一款经典的基于轮询机制来实现服务实例状态同步的开源框架，其内部的设计思想和底层原理还是值得我们深入分析，尤其是在缓存设计和增量获取更新内容等方面的实现技巧对于日常开发过程而言也非常具有参考价值，我们可以基于这些技巧来应对类似场景下的实现需求。&lt;/p&gt;
&lt;p&gt;我们在对 Eureka 进行进一步展开，可以得到如下图所示的注册中心模型图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-14-11-49-56-f1e2c186fcc9851831ee5522b37409a7-3e989.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 605px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.93388429752066%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACBElEQVQoz3VSi24SURDd36X4io0xxv6A9jeMH1AVLQJreVUohaUtCizse/cusLwqTwscz27TxLQ6ycl9zZyZO3Mkx87CNDKEDNf5BkM/h/B78AnHLsMy07zLcC9D13NYrZYIbbfbRbhvki+yUNsJNH9+JGEK7VYes9kc4/GESXLodm7fHCsF285jvV5FgdvthoTbByBhH8PhmJl/P8gWFhBiNl9gNJrAdX0MghHPS/zPJMOsoa2W0GgU4DiXEKKBnn+Ffq8B17uCaSq4uGCl3Qp0o0rSOjxC+A2EsZatwLQUrnUYRo0VnrxA51McraM92MdxdE4PSfyV/UqgKx/A+LyH5lEMdvIxtMwruFYCFlujFg5h8s1IPoN5/ARW6jl+KO8gDbL7DHoEK/kUXnIP3eIb9vA7guEJdPk1HCZpf4hFq55+iaCXwq95mX5v4X6JsZAY4+MQ8j5al+8h6UYZml4iyuhoJX6pin5f4ZSr/NIZ1E4R1Rorpp9pVaK3ARH6GTwr9TSaHGTIoapFSP9q7GKxhuMKft2Hplns55DT3Ua4s80GWC5vMJnM4Hl99tjEnMOS7vT0N0Jbr2/giwpCWYU67fk52FYR19dTBq44gHPeZSP9+iIP4RUwnQ5vK4zkcU+soYyEOCNJGrqWpkZD4Z8iCAaRfEJCz6XYtRQ1KnOfI2GAP6n00ZP5fFL4AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 14 11 49 56&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-14-11-49-56-f1e2c186fcc9851831ee5522b37409a7-3e989.png&quot; data-srcset=&quot;/static/2024-11-14-11-49-56-f1e2c186fcc9851831ee5522b37409a7-4bd12.png 200w,
/static/2024-11-14-11-49-56-f1e2c186fcc9851831ee5522b37409a7-7acd9.png 400w,
/static/2024-11-14-11-49-56-f1e2c186fcc9851831ee5522b37409a7-3e989.png 605w&quot; data-sizes=&quot;(max-width: 605px) 100vw, 605px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，Eureka 有以下几个操作与服务治理直接相关。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务注册（Register）：将各种元数据注册到注册中心。&lt;/li&gt;
&lt;li&gt;服务续约（Renew）：发送定时心跳进行续约。&lt;/li&gt;
&lt;li&gt;服务下线（Cancel）：客户端主动下线。&lt;/li&gt;
&lt;li&gt;服务剔除（Eviction）：客户端在长时间没有续约的情况下被动下线。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，遵循上一讲中给出的注册中心的基本模型，Eureka 客户端也具备本地缓存机制。Eureka 在实现这一过程中使用了非常巧妙的实现方法，我们在后续内容中会具体展开。&lt;/p&gt;
&lt;p&gt;在介绍 Eureka 时，我们采用的思路与上一节中介绍 Zookeeper 不同。在介绍 Zookeeper 时，我们更多的是从 Zookeeper 这一特定工具本身出发进行展开，而这里我们的切入点是注册中心各个角色本身的特点以及它们之间的交互流程。我们首先来看 Eureka 服务器端的工作原理。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-11&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;eureka-服务器端基本原理&quot;&gt;&lt;a href=&quot;#eureka-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Eureka 服务器端基本原理&lt;/h3&gt;
&lt;p&gt;对于 Eureka 服务器端，我们关注两个方面，即服务实例元数据的存储和缓存。&lt;/p&gt;
&lt;p&gt;如同讨论 Zookeeper 一样，对于一个注册中心而言，我们首先需要关注它的数据存储方法。在 Eureka 中，用于保存服务实例信息的数据结构实际上是一个双层的 HashMap，采用的是 JDK 中线程安全的 ConcurrentHashMap，如果用图形化的表达方式来展示这种数据结构，可以参考下图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-14-11-50-55-369276a2968fc2952d566cd110ade6a0-53b00.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 608px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 39.80263157894737%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABiElEQVQoz5VRaU/CUBDs//8PGhLjEUAFNUhBMXIooJH7LnehLdIWAZEmLeO0fuGLMW4y2bdv305m9gmOA/R6Lyi8XRBhIoRK+Qal4gVUpQ03djuH2O3B+RWCQ8b1+gPzuQZVnaDfl6DrM6/ebNZw+/tkf4UgdTJo1GOoVWOo12IolyKs48QdutIdVTawz2PbNgxDhmkMmIeYzbowzaFXm6YMQdNk9HstDAcddLtNqhxR3RSKMuJjGavVwiO0bcfLlrXFeJQgwQNzlKvxQx5Hoc8T0NQkhNVqw+EWFca5vxtMJk3WtN5LU2WCqu85FPVUDwc5qjB4jmAiu64uUSwE6CpIUpEzcQjv7zoHRGRSB3jKHKLReES9XubDU2TSPt75kHw8xEv+iOTXkKQOCcLs+5F9Pub5HLnnE3IEfghdK8ulwX0oMPQpPvlB2+0XFguNtlQPbs80FdrXvV6reYtqOYhc9hippA+v+TNUK0HuXISAf4ZlWbQsotO+ouIw8rlTWg+h3boiRHwDGkFHtZEcHIcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 14 11 50 55&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-14-11-50-55-369276a2968fc2952d566cd110ade6a0-53b00.png&quot; data-srcset=&quot;/static/2024-11-14-11-50-55-369276a2968fc2952d566cd110ade6a0-17576.png 200w,
/static/2024-11-14-11-50-55-369276a2968fc2952d566cd110ade6a0-52273.png 400w,
/static/2024-11-14-11-50-55-369276a2968fc2952d566cd110ade6a0-53b00.png 608w&quot; data-sizes=&quot;(max-width: 608px) 100vw, 608px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，Eureka 按照应用名称（spring.application.name）和服务实例 Id（instanceId）来构建两层结构。然后，Eureka 使用了 Lease（租约）这个概念来对服务的元数据进行抽象。围绕 Lease，Eureka 提供了如下所示的 LeaseManager 接口来对其进行管理，该接口的方法与前面介绍的几个核心操作是一一对应的。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5237997989870103000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface LeaseManager&lt;T&gt; {
   // 服务注册
   void register(T r, int leaseDuration, boolean isReplication);
   // 服务下线
   boolean cancel(String appName, String id, boolean isReplication);
   // 服务续约
   boolean renew(String appName, String id, boolean isReplication);
   // 服务剔除
   void evict();
}`, `5237997989870103000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LeaseManager&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 服务注册&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; leaseDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isReplication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 服务下线&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; appName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isReplication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 服务续约&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;renew&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; appName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isReplication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 服务剔除&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本质上，LeaseManager 中的各个方法的执行流程都只是围绕双层的存储结构展开操作而已，比较类似。我们这里选择用于执行服务注册操作的 register 方法进行展开，register 方法非常长，我们对源码进行裁剪，得出如下所示的重点处理流程：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26255108445599973000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
   try {
      read.lock();

      // 从已存储的 registry 获取一个服务定义
      Map&lt;String, Lease&lt;InstanceInfo&gt;&gt; gMap = registry.get(registrant.getAppName());
      REGISTER.increment(isReplication);
      if (gMap == null) {
         // 初始化一个 Map&lt;String, Lease&lt;InstanceInfo&gt;&gt;，并放入 registry 中
      }

      // 根据当前注册的 ID 能找到对应的 Lease
      Lease&lt;InstanceInfo&gt; existingLease = gMap.get(registrant.getId());

      if (existingLease != null &amp;&amp; (existingLease.getHolder() != null)) {
         // 如果能找到，根据时间确定以哪个实例为准
      } else {
         // 如果找不到，设置续约数量及其阈值参数
      }

      // 创建一个新 Lease 并放入 Map 中
      Lease&lt;InstanceInfo&gt; lease = new Lease&lt;InstanceInfo&gt;(registrant, leaseDuration);
      if (existingLease != null) {
         lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
      }
      gMap.put(registrant.getId(), lease);

      synchronized (recentRegisteredQueue) {
         // 同步更新 recentRegisteredQueue
      }

      // 处理服务的 InstanceStatus
      registrant.setActionType(ActionType.ADDED);

      // 更新服务最新更新时间
      registrant.setLastUpdatedTimestamp();

      // 刷新缓存
      invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
   } finally {
      read.unlock();
   }
}`, `26255108445599973000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstanceInfo&lt;/span&gt; registrant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; leaseDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isReplication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      read&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 从已存储的 registry 获取一个服务定义&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lease&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstanceInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; gMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAppName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      REGISTER&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isReplication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gMap &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 初始化一个 Map&amp;lt;String, Lease&amp;lt;InstanceInfo&gt;&gt;，并放入 registry 中&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 根据当前注册的 ID 能找到对应的 Lease&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Lease&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstanceInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; existingLease &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingLease &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingLease&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHolder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果能找到，根据时间确定以哪个实例为准&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果找不到，设置续约数量及其阈值参数&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 创建一个新 Lease 并放入 Map 中&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Lease&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstanceInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; lease &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lease&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InstanceInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registrant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; leaseDuration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingLease &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         lease&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setServiceUpTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;existingLease&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getServiceUpTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      gMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lease&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recentRegisteredQueue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 同步更新 recentRegisteredQueue&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 处理服务的 InstanceStatus&lt;/span&gt;
      registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setActionType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ActionType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ADDED&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 更新服务最新更新时间&lt;/span&gt;
      registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLastUpdatedTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 刷新缓存&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;invalidateCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAppName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVIPAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; registrant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSecureVipAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      read&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，整个 register 方法完成的就是对 Map 中数据的更新过程，这里我们针对服务注册的各个核心操作添加了注释。需要注意的是，在更新完 Map 中数据之后，Eureka 还会通过 invalidateCache 执行刷新缓存操作，这就引出我们接下来要讨论的话题，即 Eureka 服务缓存机制。&lt;/p&gt;
&lt;p&gt;Eureka 服务器端组件的另一个核心功能是提供服务列表，为了提高性能，服务列表在 Eureka 服务器会缓存一份，并通过一定的定时机制进行更新。&lt;/p&gt;
&lt;p&gt;在 Eureka 中，ApplicationResource 类提供了根据应用获取注册信息的入口。我们来看该类的 getApplication 方法，核心代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;36144808356440895000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Key cacheKey = new Key(
   Key.EntityType.Application,
   appName,
   keyType,
   CurrentRequestVersion.get(),
   EurekaAccept.fromString(eurekaAccept)
);

String payLoad = responseCache.get(cacheKey);
// ....`, `36144808356440895000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Key&lt;/span&gt; cacheKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EntityType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   appName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   keyType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;CurrentRequestVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;EurekaAccept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eurekaAccept&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; payLoad &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; responseCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ....&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这里是构建了一个 cacheKey，并直接调用了 responseCache.get(cacheKey) 方法来返回一个字符串并构建响应。从命名上看，不难想象这里使用了缓存机制。在 ResponseCache 的 get 方法中，我们发现它使用了如下所示的处理策略：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26637116719304245000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Value getValue(final Key key, boolean useReadOnlyCache) {
   Value payload = null;
   try {
      if (useReadOnlyCache) {
         final Value currentPayload = readOnlyCacheMap.get(key);
         if (currentPayload != null) {
            payload = currentPayload;
         } else {
            payload = readWriteCacheMap.get(key);
            readOnlyCacheMap.put(key, payload);
         }
      } else {
         payload = readWriteCacheMap.get(key);
      }
   } catch (Throwable t) {
      logger.error(&amp;quot;Cannot get value for key : {}&amp;quot;, key, t);
   }
   return payload;
}`, `26637116719304245000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Key&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; useReadOnlyCache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt; payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;useReadOnlyCache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt; currentPayload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readOnlyCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentPayload &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentPayload&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readWriteCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            readOnlyCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readWriteCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Cannot get value for key : {}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到上述代码中有两个缓存，一个是 readOnlyCacheMap，一个是 readWriteCacheMap。其中 readOnlyCacheMap 就是一个 JDK 中的 ConcurrentMap，而 readWriteCacheMap 则是 Guava Cache 库中的 LoadingCache 类型。&lt;/p&gt;
&lt;p&gt;把缓存设计为一个只读的 readOnlyCacheMap 以及一个可读写的 readWriteCacheMap，这是一种设计上的技巧，可以更好地分离职责。但因为两个缓存中保存的实际上是同一份数据，所以，我们在不断更新 readWriteCacheMap 的同时，也需要确保 readOnlyCacheMap 中的数据得到同步。为此 ResponseCacheImpl 提供了一个定时任务 CacheUpdateTask，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15356007066723754000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private TimerTask getCacheUpdateTask() {
   return new TimerTask() {
      @Override
      public void run() {
         for (Key key : readOnlyCacheMap.keySet()) {
            try {
               CurrentRequestVersion.set(key.getVersion());
               Value cacheValue = readWriteCacheMap.get(key);
               Value currentCacheValue = readOnlyCacheMap.get(key);
               if (cacheValue != currentCacheValue) {
                  // 更新 readOnlyCacheMap
                  readOnlyCacheMap.put(key, cacheValue);
               }
            } catch (Throwable th) {
               // ...
            }
         }
      }
   };
}`, `15356007066723754000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimerTask&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCacheUpdateTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimerTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Key&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; readOnlyCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keySet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token class-name&quot;&gt;CurrentRequestVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt; cacheValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readWriteCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token class-name&quot;&gt;Value&lt;/span&gt; currentCacheValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readOnlyCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheValue &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; currentCacheValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token comment&quot;&gt;// 更新 readOnlyCacheMap&lt;/span&gt;
                  readOnlyCacheMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cacheValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; th&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，这个定时任务主要是从 readWriteCacheMap 更新数据到 readOnlyCacheMap。&lt;/p&gt;
&lt;h3 id=&quot;eureka-客户端基本原理&quot;&gt;&lt;a href=&quot;#eureka-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Eureka 客户端基本原理&lt;/h3&gt;
&lt;p&gt;介绍完服务器端基本原理，我们来看 Eureka 客户端组件的实现方式。服务的提供者和消费者都是注册中心的客户端，其中服务的提供者主要完成服务注册等操作，这部分的内容主要依赖于前面介绍的服务端能力，我们不再具体展开。在接下来的内容中，我们重点关注的是服务的消费者如何定时获取位于服务器上的服务实例信息，以及如何构建属于它自身的本地缓存。&lt;/p&gt;
&lt;p&gt;对于 Eureka 而言，作为客户端核心组件的 DiscoveryClient 类具备缓存功能。DiscoveryClient 中的 initScheduledTasks 方法用于初始化各种调度任务，对于缓存刷新而言，调度器的初始化过程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;73608548307805120000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`if (clientConfig.shouldFetchRegistry()) {
   int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
   int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
   scheduler.schedule(
      new TimedSupervisorTask(
         &amp;quot;cacheRefresh&amp;quot;,
         scheduler,
         cacheRefreshExecutor,
         registryFetchIntervalSeconds,
         TimeUnit.SECONDS,
         expBackOffBound,
         new CacheRefreshThread()
      ),
      registryFetchIntervalSeconds,
      TimeUnit.SECONDS
   );
}`, `73608548307805120000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clientConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldFetchRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; registryFetchIntervalSeconds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; clientConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRegistryFetchIntervalSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; expBackOffBound &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; clientConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCacheRefreshExecutorExponentialBackOffBound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimedSupervisorTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&quot;cacheRefresh&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         scheduler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         cacheRefreshExecutor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         registryFetchIntervalSeconds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SECONDS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         expBackOffBound&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheRefreshThread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      registryFetchIntervalSeconds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SECONDS
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，这里启动了一个调度任务并通过一个 CacheRefreshThread 线程完成具体操作，系统默认是每隔 30 秒刷新本地存储的服务实例数据。在这个线程中，我们发现在进行一系列的校验之后，最终调用了 DiscoveryClient 中的 fetchRegistry 方法完成对服务注册信息的获取。该方法如下所示（为了简单起见，对代码进行了部分裁剪，只保留主流程）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;77611496646435650000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private boolean fetchRegistry(boolean forceFullRegistryFetch) {
   try {
      // 获取应用
      Applications applications = getApplications();
      if (condition) { // 如果满足全量拉取条件
         // 全量拉取服务实例数据
         getAndStoreFullRegistry();
      } else {
         // 增量拉取服务实例数据
         getAndUpdateDelta(applications);
      }

      // 重新计算和设置一致性 hashcode
      applications.setAppsHashCode(applications.getReconcileHashCode());
   }

   // 刷新本地缓存
   onCacheRefreshed();
   // 基于缓存中的实例数据更新远程实例状态
   updateInstanceRemoteStatus();
   // 注册表拉取成功后返回 true
   return true;
}`, `77611496646435650000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; forceFullRegistryFetch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 获取应用&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Applications&lt;/span&gt; applications &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getApplications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 如果满足全量拉取条件&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 全量拉取服务实例数据&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;getAndStoreFullRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 增量拉取服务实例数据&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;getAndUpdateDelta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;applications&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 重新计算和设置一致性 hashcode&lt;/span&gt;
      applications&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAppsHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;applications&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReconcileHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 刷新本地缓存&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;onCacheRefreshed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 基于缓存中的实例数据更新远程实例状态&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;updateInstanceRemoteStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 注册表拉取成功后返回 true&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码中带注释的几个方法都非常有用，因为 getAndStoreFullRegistry 方法的逻辑相对比较简单，我们将重点介绍 getAndUpdateDelta 方法，以便学习在 Eureka 中如何实现增量数据更新的设计技巧。裁剪之后的 getAndUpdateDelta 方法代码下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;10323265940841742000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void getAndUpdateDelta(Applications applications) throws Throwable {
   long currentUpdateGeneration = fetchRegistryGeneration.get();

   Applications delta = null;
   // 通过 eurekaTransport.queryClient 获取增量信息
   EurekaHttpResponse&lt;Applications&gt; httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
   if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
      delta = httpResponse.getEntity();
   }

   if (delta == null) {
      // 如果增量信息为空，就直接发起一次全量更新
      getAndStoreFullRegistry();
   } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { // 通过 CAS 来确保请求的线程安全性
      String reconcileHashCode = &amp;quot;&amp;quot;;
      if (fetchRegistryUpdateLock.tryLock()) {
         try {
            // 用 Eureka 返回的增量数据和本地数据做合并操作
            updateDelta(delta);

            // 用合并了增量数据之后的本地数据来生成一致性 hashcode
            reconcileHashCode = getReconcileHashCode(applications);
         } finally {
            fetchRegistryUpdateLock.unlock();
         }
      } else {
         // ...
      }

      // 比较本地数据中的 hashcode 和来自服务器端的 hashcode
      if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
         // 如果 hashcode 不一致，就触发远程调用进行全量更新
         reconcileAndLogDifference(delta, reconcileHashCode);
      }
   } else {
      // ...
   }
}`, `10323265940841742000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAndUpdateDelta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Applications&lt;/span&gt; applications&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; currentUpdateGeneration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fetchRegistryGeneration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;Applications&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 通过 eurekaTransport.queryClient 获取增量信息&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;EurekaHttpResponse&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Applications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; httpResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; eurekaTransport&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queryClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDelta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remoteRegionsRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;httpResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatusCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OK&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatusCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; httpResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果增量信息为空，就直接发起一次全量更新&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;getAndStoreFullRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetchRegistryGeneration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compareAndSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentUpdateGeneration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentUpdateGeneration &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 通过 CAS 来确保请求的线程安全性&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; reconcileHashCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fetchRegistryUpdateLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 用 Eureka 返回的增量数据和本地数据做合并操作&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;updateDelta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 用合并了增量数据之后的本地数据来生成一致性 hashcode&lt;/span&gt;
            reconcileHashCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getReconcileHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;applications&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            fetchRegistryUpdateLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 比较本地数据中的 hashcode 和来自服务器端的 hashcode&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;reconcileHashCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAppsHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; clientConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldLogDeltaDiff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果 hashcode 不一致，就触发远程调用进行全量更新&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;reconcileAndLogDifference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;delta&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reconcileHashCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结合 Eureka 服务器端基本原理，我们知道 Eureka 服务器端会保存一个服务注册列表的缓存。在 Eureka 官方文档中提到 Eureka 服务器会把更新数据保留 3 分钟，而 Eureka 客户端通过前面介绍的定时机制会每隔 30 秒刷新本地缓存。原则上，只要 Eureka 客户端不停地获取服务器端的更新数据，就能保证自己的数据和 Eureka 服务器端的保持一致。但如果客户端在 3 分钟之内没有获取更新数据，就会导致自身与服务器端的数据不一致。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，Eureka 采用了一种叫做一致性 HashCode（ReconcileHashCode） 的实现机制。Eureka 服务器端每次返回的增量更新数据中都会带有一个 HashCode，Eureka 客户端用本地服务实例信息算出的 HashCode 应该和 Eureka 服务器返回的一致，若不一致就证明增量更新出现了问题，导致客户端和服务器端上的服务实例信息不一致了，此时需要全量更新。&lt;/p&gt;
&lt;p&gt;在 Eureka 中，计算一致性 HashCode 的方法如下所示，可以看到这一方法基于服务实例信息完成一个 String 类型的 HashCode 计算过程。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34552753018952310000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public static String getReconcileHashCode(Map&lt;String, AtomicInteger&gt; instanceCountMap) {
   StringBuilder reconcileHashCode = new StringBuilder(75);
   for (Map.Entry&lt;String, AtomicInteger&gt; mapEntry : instanceCountMap.entrySet()) {
      reconcileHashCode.append(mapEntry.getKey()).append(STATUS_DELIMITER).append(mapEntry.getValue().get())append(STATUS_DELIMITER);
   }
   return reconcileHashCode.toString();
}`, `34552753018952310000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getReconcileHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; instanceCountMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt; reconcileHashCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; mapEntry &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; instanceCountMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entrySet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      reconcileHashCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;STATUS_DELIMITER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;STATUS_DELIMITER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; reconcileHashCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;作为总结，Eureka 客户端缓存定时更新的流程下所示，可以看到它与服务注册的流程基本一致。也就是说在 Eureka 中，服务提供者和服务消费者作为 Eureka 服务器的客户端采用了同一套体系完成与服务器端的交互。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-14-11-51-36-75aeeb503563a485f1db74a49fb314b2-4c6f5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 607px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 71.16968698517299%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAB0UlEQVQ4y4VTiW7aQBDl/3+kSlSl6pH+QltyFOKiWEiADZg4+ADbGGMMfp030XLmWGk16903b94cblgPP/DQvkLr72fYjz/heQ5+/7rA3e0lmn8+yds1plMPdzeXaLeuYLW/CO4bOv+uMZk4aN1/xe3NhW778TsaUTRBEIzg+w6yNEBVrTGb+Xj2XUy8PqLoCUWxRBiOEUee4iKxccz7XO302ZWgQ/F7QgOyilWJLMsFGGM0GsljgCRJhXyD01VV27O7uga221rPSjifz0RBgEWWSZQZ0jSVdDy5nyPPc/2mrcVztVqJ81bP3EKH9Xotu9SzEiZJIgqzo6gk6Pf7GA6HUpJASbno/EKEnd1sNqK82iskoXFgdK7lcolOpyM1ipWE30VRqMJTQpIR8yqhAS0WCziOg16vp9bzPFVqCPcpvxC+qdCAjELa46ZUO4xZzOpDhfxmumySbdtwXVcaF2I8Hu/eoyjSMrD+ZVm+T0iQSc80go3qdrtKSttsNtValrXL5E1COg8GA60bZ5N1JI53PFMZx4p+YRBq8A8JOTaMbDpM1b7vnw22mdODwZZIyXkNTdT9H1FrTV/r8lFTGPlUIckM4aHz4WCbNxISywH/D0/bM2SPuh2qAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 14 11 51 36&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-14-11-51-36-75aeeb503563a485f1db74a49fb314b2-4c6f5.png&quot; data-srcset=&quot;/static/2024-11-14-11-51-36-75aeeb503563a485f1db74a49fb314b2-3bd24.png 200w,
/static/2024-11-14-11-51-36-75aeeb503563a485f1db74a49fb314b2-52f0c.png 400w,
/static/2024-11-14-11-51-36-75aeeb503563a485f1db74a49fb314b2-4c6f5.png 607w&quot; data-sizes=&quot;(max-width: 607px) 100vw, 607px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Eureka 客户端和服务器端的差异化信息计算和获取过程，是这种更新机制所必须要考虑的问题，也是我们自己在设计类似场景时的注意点。我们可以从上述实现原理中掌握优秀开源框架底层的开发技巧。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-13&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;对于本讲中所讨论的话题，面试过程中第一个需要明确的要点就是基于定时更新机制的注册中心实现机制。相较上一讲介绍的推送机制，定时更新机制的概念容易理解，基本的实现策略也比较简单。通常，我们采用一些定时器实现工具和框架对位于服务器上的服务实例信息进行轮询即可。&lt;/p&gt;
&lt;p&gt;但是需要注意，定时更新机制有它自身的复杂度，我们需要综合考虑定时策略、同步数据、同步效率等问题，这些问题是这种机制所特有的，在面试过程中需要重点对这些机制展开讨论。这是面试官的考查重点，也能很好展现候选人的竞争力。对于这些机制的讨论，我们可以分成两个步骤，首先阐述这些机制背后的设计思想，然后再来结合具体框架给出底层的实现原理分析。&lt;/p&gt;
&lt;p&gt;至于具体的开源框架，和上一讲中所提到的 Zookeeper 相比，定时更新机制类框架采用的是另一套完全不同的实现策略。我们在回答过程中需要基于特定的技术要点来进行分析。&lt;/p&gt;
&lt;p&gt;举例来说，针对 &lt;code class=&quot;language-text&quot;&gt;Eureka 客户端从服务器端获取注册信息&lt;/code&gt; 这一特定的细节，回答难度实际上很大。而至于为什么要问这一特定细节，在于 Eureka 在实现这一细节上提供了一种处理增量数据的开发技巧和工程实践，值得我们深入学习和应用。我们知道，当执行轮询操作时，需要考虑的一大问题就是如何高效地从服务端获取最新的服务实例信息。为了提高效率、降低网络通信的开销，一般都会考虑引入 &lt;code class=&quot;language-text&quot;&gt;增量&lt;/code&gt; 数据获取的设计思想，Eureka 也采用了这种机制。具体来讲，Eureka 采用了一种叫做一致性 HashCode 的实现机制。&lt;/p&gt;
&lt;p&gt;如果我们能够结合本讲中介绍的这种增量式数据获取方式，那么相信能够获得面试官的更多认同。这也是我在具体面试过程中的一条经验，即对于那些普通的知识点，建议不用过多展开，但我们可以多围绕框架本身所具备的一些特有的设计和开发技巧给出自己的分析和总结。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-11&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲介绍了实现注册中心的另一种常见的策略，即基于定时更新策略来实现客户端与服务端之间元数据的有效同步。这是很多注册中心所采用的实现策略，设计思想简单，应用也很广泛。我们结合老牌的注册中心开源框架 Eureka 对这一主题进行了详细展开，并分析了该框架在实现这一主题过程中所具备的开发技巧。一方面，这些开发技巧是面试过程中的回答要点，另一方面，我们也可以结合具体的场景把这些开发技巧应用到日常开发过程中。&lt;/p&gt;
&lt;p&gt;在分布式系统中，当服务的数量达到一定规模时，我们就不建议客户端对这些服务直接发起远程请求了。这时候，我们可以引入服务网关这一技术组件。那么，如何实现一款高性能服务网关？下一讲将围绕这个话题展开讨论。&lt;/p&gt;
&lt;h1 id=&quot;服务网关：如何实现一款高性能服务网关？&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E6%AC%BE%E9%AB%98%E6%80%A7%E8%83%BD%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务网关：如何实现一款高性能服务网关？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们引入了注册中心这一技术组件来构建分布式系统。通过注册中心，我们可以对系统中存在的服务进行有效治理。一旦服务得到了治理，下一步就可以对这些服务发起访问了。&lt;/p&gt;
&lt;p&gt;这个过程并不复杂，但也没有我们想象的那么简单，因为客户端请求和服务端所提供的访问入口之间需要进行粒度匹配、路由、安全等一系列控制。为了实现这些控制，服务网关（Gateway） 也就应运而生了。&lt;/p&gt;
&lt;p&gt;那么，什么是服务网关？如何构建一款高性能服务网关？这是面试过程中的常见话题。本讲内容将给出这些问题的具体答案。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-11&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-11&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式架构中，如果客户端和各个服务直接进行交互，可能会出现很多问题。通常，每个服务的职责在于提供某一个业务领域的访问入口，而客户端则一般都需要整合多项业务数据。这就会导致客户端的请求和服务提供的访问入口在粒度上是不一致，导致客户端不得不发送多个请求才能获取所想要的数据。另一方面，在这种场景下，如果某个服务发生了调整，很可能会导致客户端需要做相应的调整，这显然是不合理的。&lt;/p&gt;
&lt;p&gt;下图展示了服务版本升级的一种演进结构：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-11-49-42-ab87944809955116971430f08842bc63-e49e6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 639px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 51.79968701095462%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAB7klEQVQoz52Sa1PaYBCF8/9/RKfTUSuodarTOlqrbe0IRi5KCCSSEAIVEA0h0CYkEJ4umfqp9UvPzJnd95LN7nmPslqteIlrNHsmmzc5Nkrb7NT32dJ2UO+u8R89cldXbN3eslWpkNcbvNd1FF5AskiIozmFapFT45zj2iln5lfOrW9YA5tw9osL0+Ck0eCTVuezaVJoOyieN8LzhozHD8Ihj48DfN8nCAJmsxlRGJEmKavlinSRsoyXsPrz1+VS9hZyJjFJsly5qe7idg7RtTxtax/bOqLX+8H/Qum6Bo5TE2oYTRXDqGSdTadTgsmEmqFRbKsUbRXVKVF0VLqjLos4oWRZsnbkzEbtdKi6Lspae+mYVGKaIoVCJhOfMAoJf4YcfT9mU8vx6vINm5Uc2809VKuE9/BE7lplo3rD60KBtzWNQ8NE0WpH2PYJev0Dd6bk1hdct0u/3xdNx1mn48DHnwoler5HNI+y8aYyyVi0nmR3AgKJSrNxLkXOaOgnNBvHkl8wGo0y26yLRVH0T62ebfWXhnG8IAxj5vNEPo6FYpc4zl55zbpe57JcoNVt4Qw6tHpW1u0a3eEA6/6etkxj9+9xB4OXffiMcqvKjrmfmfvd3QF79gHX7TJPwxH5com8+G9bfLjbavFRHuc3ESTiNCYOongAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 11 49 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-11-49-42-ab87944809955116971430f08842bc63-e49e6.png&quot; data-srcset=&quot;/static/2024-11-15-11-49-42-ab87944809955116971430f08842bc63-12809.png 200w,
/static/2024-11-15-11-49-42-ab87944809955116971430f08842bc63-55d6b.png 400w,
/static/2024-11-15-11-49-42-ab87944809955116971430f08842bc63-e49e6.png 639w&quot; data-sizes=&quot;(max-width: 639px) 100vw, 639px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;li&gt;如果让你在服务网关中添加安全性控制机制，你会怎么做？&lt;/li&gt;
&lt;li&gt;如果想要在网关层对请求进行限流，可以采用怎么样的实现方式？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述问题都很经典，是面试过程中的常考题，需要你引起重视。接下来，我们从面试角度出发对这些问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-12&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;在当下的分布式系统开发过程中，服务网关已经成为必不可少的一个技术组件。关于服务网关的相关工具和框架也层出不穷。虽然，每个网关工具在部署和使用方式上各有不同，但它们的架构本质上是非常类似的。和前面介绍的注册中心相比，服务网关在定位上更偏向是一种底层的中间件，通用性很强，所以我们可以比较容易地对它的作用、组成结构以及功能特性进行抽象和提炼。这部分内容更多的是理论知识，需要候选人熟练掌握。&lt;/p&gt;
&lt;p&gt;掌握了概念之后，接下来就是具体的开源框架。&lt;/p&gt;
&lt;p&gt;对于服务网关而言，实际上它所具备的核心功能还是请求转发机制，所以，很多请求响应类的框架所要面临的技术难点服务网关同样也需要面对。典型的就是性能问题，如何构建高性能的请求转发机制是我们在分析具体网关框架时的一个切入点，也是面试过程中的常见考点。&lt;/p&gt;
&lt;p&gt;最后，服务网关类的面试题经常会考查候选人对某一个特定功能特性的掌握程度，比方说网关的路由机制、安全性机制、限流机制等。候选人在回答这些问题时，需要结合自己擅长的框架做深入的研究，并能够从原理出发给出自己的总结和思考。&lt;/p&gt;
&lt;p&gt;在本讲中，我们将要深入分析的是 Spring Cloud 家族的 Spring Cloud Gateway 框架。而在此之前，让我们先从服务网关的基本概念开始讲起。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-14&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;服务网关的概念本质上不难理解，从设计模式上讲，我们也可以把服务网关看作是门面（Facade）模式的一种具体表现形式。门面模式的作用就是把复杂度屏蔽在系统内部，从而降低耦合度，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-11-52-03-d61e53ea6581c728102aa4655dfce2e6-cd472.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 557px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.01436265709155%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAChklEQVQoz3WTeVPaUBTF8/0/Qeu002nHuhQEpIWALCFhD5vsSgja6VDFREEBBQL99bZj/+ybufNu7jvn3POWKKWin4v+Ked1HxXziH43QK+rs9lAq5mQPCBrfszSIZ32Cc2mymK5xrIq8u3nvOGnVjkWrJ9G/RTlvBFhcKlK4ZSqecJl/xvtVobdDkYjE9uO0++pQjzFGqhSK7Be77i8LItgUHhhEQrRaYUFd4ay2XisVmvWMm+3W+m+xHFdHOeOq6HNg3PPRbdHt9Vm6t5zbY+4m0xwJZ/PF3jelo3nCX8jjTYo/Bu/XuN19CybYKdF8d4lPrJRhxYF1yF80acqDf7HU1arFam2RtrWydgGmqVjXBn4zDhZ2VpNQMbzmm/jCVXJTYljM4k+0klbrzGU6GZYLBYo08cZ4e8qReqkngtoqxKFXYXAUOWwlSNk1zju5DkZmASHVT6UUnyxYuhemaxXofirSoUmkXGcG+cW5WnxJMAIuasonesY5X6I/dI+uZUpjhoYyzzao44pTf5Ujuqfycntdn+ckRZsYVMVK23iP1NM3AnKYjnnsBOgKYJ4dW5/JtFuU+ReyoStMG+Te6RcQ6SaIt9Du9MYjxNsX0ReBPNeTVZ6xIT3V3D2NCP6I0F6anDQOMbXOSH+YJCS9/cyy7N7Nkn3gujSwHjKExiE2cu85+u1ypv0O5JOhuyyhDr+JyhnGBX1zG2SscwP0yxqeR/f+Rdq32NUr6PojkZGiD1xdDdJU7wIkVkWKYsB909dducTA87MQZnP54TlwYbEWalyRE3+mEjpM4n7LLGJRvQmLWdYIDL4imZ8pNUNESsfyGVFSeQ+0ewE0cwDjqo+Hh6n/AYFYP7gR5T9jgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 11 52 03&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-11-52-03-d61e53ea6581c728102aa4655dfce2e6-cd472.png&quot; data-srcset=&quot;/static/2024-11-15-11-52-03-d61e53ea6581c728102aa4655dfce2e6-8e829.png 200w,
/static/2024-11-15-11-52-03-d61e53ea6581c728102aa4655dfce2e6-2b773.png 400w,
/static/2024-11-15-11-52-03-d61e53ea6581c728102aa4655dfce2e6-cd472.png 557w&quot; data-sizes=&quot;(max-width: 557px) 100vw, 557px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;既然是一种门面，那么网关中显然不应该包含任何与业务相关的处理逻辑。那么，服务网关的核心作用究竟有哪些呢？主要包括三部分，即解耦、适配和数据聚合。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;解耦：从技术架构而言，任何组件之间的交互都应该具备较低的耦合度。正如我们在问题背景部分所讨论的，客户端请求和服务端访问入口之间的依赖关系需要确保独立，即服务端背后的功能演进和版本升级过程对于客户端而言应该是透明的。考虑到分布式系统中往往存在大量服务，解耦也是我们在服务体系过程中的一项核心目标。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;适配：在分布式系统中，客户端的类型也是多样的。当我们面对多个不同类型的客户端时，考虑到页面展示上的差异性，往往需要针对特定的客户端请求提供特定的响应结果，哪怕这些请求属于同一业务场景。&lt;/p&gt;
&lt;p&gt;举个例子，分页是一种常见的数据返回效果，但不同客户端对分页数量的要求可能就是不一样的，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-11-53-01-eddd488b6b1fd716e06f1e2f7fe922e1-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 26.31578947368421%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABEElEQVQY032Q22rCUBBF8/9/UQpShPrQO20VWlqkoEhsqTGYEM3NmMs5iQUbb6tDnqUDm2EeZu81YwwHl5ijNv33c8bmBc5shNZr0nTFcrkkSRJ836fUGl/mjm3zMrXgsOdUGUHwRRR94jpD6WPieE5RKDFMG8MsyyjLkt/NhnkccyMBfQngeGS725LrHFWpRoUuMPinjrJU1zVVVaGV4r7b5VpMX12HH1XSHfRoWx1aZpuzQYvb4BEjjjwhcwlDhzCYoVQmBmuhzOV03UiJaiG0PY87IX+TftjuWBUpVmQzDW0mgcUscTBc50mMnpl8XxH4D3ieSZ4rCQhZLBbND6MobgjDVUpPzv8IAtif/uEf+odyFEPBQfAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 11 53 01&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-11-53-01-eddd488b6b1fd716e06f1e2f7fe922e1-0e173.png&quot; data-srcset=&quot;/static/2024-11-15-11-53-01-eddd488b6b1fd716e06f1e2f7fe922e1-2fb9f.png 200w,
/static/2024-11-15-11-53-01-eddd488b6b1fd716e06f1e2f7fe922e1-f1e72.png 400w,
/static/2024-11-15-11-53-01-eddd488b6b1fd716e06f1e2f7fe922e1-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-11-53-33-9b00b78294f4eceb0d6e3de2e32e7b22-da4b5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 456px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 79.60526315789474%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsSAAALEgHS3X78AAADiUlEQVQ4yz2UeVfiWBDF8/2/Qs/0MtIuzVFQWQKEJBAksoZNCMgeUNuxlU1bu53zmyKO88c9qXfr1kulct9TTCNINhPAyu6+IbOLoe/QbJySPzvAlHjLZcyAj5y1R6N+IjVf32B+xTQCvu7cDqJY2b+YeTG8SYzxMEL/8pjJOMri3hQ+TqUUoHi+Q6vxjXYryGwa97V3f+uii1Cv7VMuBvz1aHCCUi6nKJwfy+5vsO0wdj4sG5vUanHp6FA6CUq3Ic4ElXKEQT9DsXAi+jfte229nkD59fwi3TywWixZPixYr9Zs1o8sHlYsl2uef74wnXgMByM/Xq02LBYr1qLZrDd+3Xvt9qlkXJfM9TXmbEbm6opEr0us1UDtXBBvt0h220SaNU7qVT/ecm+5JqrbwZzPyWwhtcZ4jBIdjmgAjsD6B2pTW7o84/tdlh8PFuOJSrtzjNs9xZul+HGf4+bWlO5zNHpx7Nfffm1VUBQoseGQtHym/vBA4u4etW6RdeKUhzblcRG1kuDUjhIrxkk6Kex+nsa8Qr5rEa3qJO6XGI/PpJaP2L9fUXZzJtPrLK32CbVmGKd1Sq8fRx330Lpl3JGB09MpuxrdaZbzYYHDsUd9lMOdWnRGJq2BzsAzSV9WUYKlAs60SLaeRK+qWC2NSs9Av/HIjxy6wzSFZgy7FqE3Mah5JSIyb3dmy2YGlU6ScjvB5TSD3q+jHDoVSrM6xVGJgsAeCC7zZO5vSQ6aGLUUetNAq2no9RRpt0haxqO3LDQZgeZopAR6LUly5KLsZHXmt1k67gmNVohaIyQGP8W8GWHLC+bTmM9XnW9MRlHp7IyQ/E3vRmo6IRrNI4rlfW6u5Au7RRStfUG02yG2tYLbRpU4ctFE9zzpsE/sokG8I1Zx/7NK18UUi8S2nM+3SEhuq0tITnl9+cVKDLxePf6PzWpr2JWPn08veNOZHLMpTxKvlhuWYuCt5l2/Nftm/cSj5BTDMUmV4hhij3RJRasImmk0ifVqQvgkx7kQR9aRr9EFmlhIk9mmtuuy6vPJQlRmraMcmJ+YX6kyo105o2FG1ynCpR2u5irlSoCpXBQduTx0yXuTqM99v05yfinnuL7PUC4Tx9njeqaSlctC2dd3Ocp8Zi/xgaD2kVAuwJ4VIGx8YV/9QCj9iUNrhz3jMyH9M/uiC0scFN2BLjntTw6Sf0juI8HCAf8CX9lEQ1W8+b8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 11 53 33&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-11-53-33-9b00b78294f4eceb0d6e3de2e32e7b22-da4b5.png&quot; data-srcset=&quot;/static/2024-11-15-11-53-33-9b00b78294f4eceb0d6e3de2e32e7b22-e9143.png 200w,
/static/2024-11-15-11-53-33-9b00b78294f4eceb0d6e3de2e32e7b22-e55e7.png 400w,
/static/2024-11-15-11-53-33-9b00b78294f4eceb0d6e3de2e32e7b22-da4b5.png 456w&quot; data-sizes=&quot;(max-width: 456px) 100vw, 456px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;总体而言，服务网关为分布式环境下的请求处理过程提供了强大的定制化技术能力，常见的扩展性、伸缩性、安全性和可用性等架构设计上的技术点在网关中都能得到体现。&lt;/p&gt;
&lt;p&gt;从扩展性和伸缩性角度讲，网关提供了服务路由支持，我们可以在网关层添加各种路由规则，确保来自客户端的请求能够以合适的方式发送到目标服务。在这个过程中，我们可以很轻松地实现请求转发、系统扩容等操作。&lt;/p&gt;
&lt;p&gt;一般而言，对于来自客户端的任何请求都需要考虑安全性，而服务网关最适合来完成这部分操作。实现访问安全性的前提是用户认证，我们可以在网关层添加各种身份认证、黑白名单、Token 校验等常见的安全性控制手段。&lt;/p&gt;
&lt;p&gt;最后，我们需要强调一下服务网关所具备的访问控制功能。在高并发系统中，我们通常会采用限流和降级等手段来防止服务出现不可用。这时候，我们就可以在服务网关上设置对应阈值或规则，从而确保来自客户端的请求不会流转到后台服务。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-12&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;介绍完服务网关的作用和结构之后，我们接下来讨论具体的实现工具。这里，我们来到 Spring 家族中的 Spring Cloud Gateway 框架，这是目前最主流的服务网关之一。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-gateway-架构&quot;&gt;&lt;a href=&quot;#spring-cloud-gateway-%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Gateway 架构&lt;/h3&gt;
&lt;p&gt;Spring Cloud Gateway 的核心功能是对 Web 请求进行路由和过滤，其内部大量依赖于 Spring 中的响应式 Web 框架 WebFlux。&lt;/p&gt;
&lt;p&gt;在接下来的内容中，我们先来分析 Spring WebFlux 技术栈，然后在此基础上引出 Spring Cloud Gateway 的整体架构。针对 Spring Cloud Gateway 这款框架，我建议你把它和 WebFlux 对比起来一起分析，这是一种非常有效的学习方法。&lt;/p&gt;
&lt;p&gt;虽然 WebFlux 和 WebMVC 是两个时代的技术体系，但事实上，Spring 也为传统 WebMVC 中的 HandlerMapping、HandlerAdapter 等组件提供了对应的响应式版本。这也体现了 Spring 所采用的一种设计理念，即充分利用现有组件进行增强，而不是轻易地替换。&lt;/p&gt;
&lt;p&gt;我们知道传统的 Servlet API 是阻塞式的，所以我们需要引入 ServerHttpRequest 和 ServerHttpResponse 这两个对象。在 WebFlux 中，它们分别代表着请求和响应本身。类似的，作为请求处理的核心组件，我们也需要引入一个新的过滤器链 WebFilterChain，定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84942437794743550000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface WebFilterChain {
   Mono&lt;Void&gt; filter(ServerWebExchange exchange);
}`, `84942437794743550000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebFilterChain&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，我们在这里并没有看到 ServerHttpRequest 和 ServerHttpResponse 这两个对象。实际上，上述代码中的 ServerWebExchange 内置了这两个对象，相当于是一个请求上下文。另一方面，我们发现 filter 方法返回的是一个 Mono 对象，也就意味着整个调用链路使用了响应式编程的开发模式。&lt;/p&gt;
&lt;p&gt;前面提到，HandlerMapping、HandlerAdapter 等核心对象在 WebFlux 中都能找到对应的实现，我们接下来看一下它们的定义，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13079549406096658000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface HandlerMapping {
   Mono&lt;Object&gt; getHandler(ServerWebExchange exchange);
}

public interface HandlerAdapter {
   Mono&lt;HandlerResult&gt; handle(ServerWebExchange exchange, Object handler);
}`, `13079549406096658000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerMapping&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HandlerAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HandlerResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;类似的，WebMVC 中的 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 等对象也同样存在响应式版本。但是请注意，WebFlux 还专门提供了一套新的技术组件用来提供函数式编程的开发模式，这套组件就是 RouterFunctionMapping 和 HandlerFunctionAdapter。&lt;/p&gt;
&lt;p&gt;整个 WebFlux 的架构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-14-23-43-fc69bd1637b6a85d3491d2a1529a1615-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.53250773993808%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABQUlEQVQoz31QDW+CMBDt//9DSzY3MycuJsvGiICC0PI1KB8CaoxT8Xl2Jssy4yXXa3vv3b07hit2PB5VbOoMgejDtu7gzO7B/Qc0TXzBdHSo2x8uO5P/e6eS2+0GWSbgexbm7gQy49hsVirXdR2ucRluWNsWiKMhpnYPs2mP1D5j2aa3KGBNk6KqIuR5gKIISYVAXSdYLgus1y39ewgCk1QayOUcq1VN+Zz+heKU5S+3pWYslxaSWKeRxqRCo1290pifKHKXgDEEPyt8Iu+D84EihtTAdTTCj2BOBiqeeTIzwJLYgBBvRNBgmS8EHFODD0jpYL8/YLc70L6g/Hv3844ik4oMMaMiuv54ETJCkuhgVSVIkUO7spGmDsLQojE8LBYJ0q8AVamDexpNMERZvBOGk/KQilqIQlvhfd9QLqWLE6ceCvPn8y+FAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 14 23 43&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-14-23-43-fc69bd1637b6a85d3491d2a1529a1615-0e173.png&quot; data-srcset=&quot;/static/2024-11-15-14-23-43-fc69bd1637b6a85d3491d2a1529a1615-2fb9f.png 200w,
/static/2024-11-15-14-23-43-fc69bd1637b6a85d3491d2a1529a1615-f1e72.png 400w,
/static/2024-11-15-14-23-43-fc69bd1637b6a85d3491d2a1529a1615-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;对 WebFlux 有了基本认识之后，我们来讨论 Spring Cloud Gateway。就请求响应模式而言，可以说 Spring Cloud Gateway 和 WebFlux 采用的是完全一样的处理方式。但 Spring Cloud Gateway 内部也提出了两个核心概念，一个是过滤器（Filter），一个是谓词（Predicate），它的整体架构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-14-24-14-97ff936687558c377839126f177a3d9e-8fcec.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 645px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.29457364341086%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAAB0ElEQVQoz12SWW/aQBSF+f8/olKXPJCqREEgKHZYkiiErWG3McbGNmCMwZiyVVD4ajt9aY/0aeZqrkZz7pnY9Xol5BIQSppOycxs4l2Zj9UGN2/tgA4fyjW+NFrB2YKMprHZ7aL+0+nE+Xzm/Psc7WPX64WQS0ColrPgyXfoOW06AQ3rBxW9RnPWjOqWKyGulqz3e46HA8lOmnjzG7etBIlmkhj/qbv0EOUK3jTNevGAbaRRul/x5gLbVZ6hlCBr6tgrj7Xnodk6+sJg7BqYrkVsOqliGmUMo8HpeKRiWDxNeiwXRSQ5jbcuU63HUYYZ+v0ksppFsCc4a5/9dkvdfKM6aURUxjViilJClgqoaoVD0FDRdQrrJc9Wj0fzHWHYpGT0KOodHicDBHfBzHX56fu8qK/k5RL5fpHn4Us4Qwjj+JtJZFnQu1hGFtspIg9S1Bu3mJbIPKhVLUtaVyLLfmBZMYeo8xGjucZoOgpnGF0XJf0eikt+3GbnFZhZObb+E4aWYj4VcGYi6uCeO6mDf/zFMQhG1Aqk1AzZsUhmmAtf+O+36dgOwkSl3svw8Jqg1vtOUvhMUrzhPveJOyFYFZnN/hD1+xsf2Rgg6TKjmcYfr/I+fNDktqEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 14 24 14&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-14-24-14-97ff936687558c377839126f177a3d9e-8fcec.png&quot; data-srcset=&quot;/static/2024-11-15-14-24-14-97ff936687558c377839126f177a3d9e-ba1e2.png 200w,
/static/2024-11-15-14-24-14-97ff936687558c377839126f177a3d9e-72011.png 400w,
/static/2024-11-15-14-24-14-97ff936687558c377839126f177a3d9e-8fcec.png 645w&quot; data-sizes=&quot;(max-width: 645px) 100vw, 645px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Spring Cloud Gateway 中的过滤器和 WebFlux、WebMVC 中的过滤器是同一个概念，都可以用于在响应 HTTP 请求之前或之后修改请求本身及对应的响应结果，区别在于两者的类型和实现方式。&lt;/p&gt;
&lt;p&gt;而所谓谓词，本质上是一种判断条件，用于将 HTTP 请求与路由进行匹配。Spring Cloud Gateway 内置了大量的谓词组件，可以分别基于 HTTP 请求的消息头、请求路径等常见的路由媒介进行自动匹配以便决定路由结果。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-gateway-工作流程&quot;&gt;&lt;a href=&quot;#spring-cloud-gateway-%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Gateway 工作流程&lt;/h3&gt;
&lt;p&gt;Spring Cloud Gateway 中的核心概念就是路由和过滤，我们通过谓词判断获取 Web 请求的路由，然后基于一组过滤器执行过滤，并最终输出结果。在讨论具体的过滤器实现过程之前，我们先来关注整个请求处理流程。&lt;/p&gt;
&lt;p&gt;我们知道 Spring MVC 中有一个核心类 DispatcherServlet，而 WebFlux 中对应的核心类是 DispatcherHandler，该类充当着所有请求的入口，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-15-14-24-47-3f55173bc46d819a3a1fd9affd65c62f-04139.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 414px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 129.95169082125605%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsSAAALEgHS3X78AAAE9ElEQVRIx3VVaXPaSBDl/3/dD1tbSVWOdbyJndjGNrcASYCQAHGDhUCAOMVpwGADNn7bGrDjTWWn6tXMaLqfumdezzien5/xK+x2d7dCqRSErrtRyF8il3VCr7hQLovYbB6Zze43vo4Xgl/bfLFEsyliNORR0WyiS4xHAoy6hPXm6bc+jNAerOZzLAn3iwXu7wiLOR6Wdxj0uui2TNQqGppGDR2zifHAwprWVmRj29r98uBvN0fWMBA2TYi9PnyNFnx1E15CoNkG1+pC6A9xksrgPF8CT+OA2YHX2NvYvZ/sxF4PfLuNSLEIx0W5jAwxc4MOcm0ZJUtFmaA2JCSNGNRmHHzWAyHnY/OkEUWxl0Kxn2J2KVNBcHaLNHH4x2M4XFWdTTyVLEb9AGbTCBazKPLZE3D+vxDhP0CWjqDEjxAKvENU+IDpWMBsEqE0Y2i3ArhuGlCJI77dwnF9IBTulviWz+KsXMAPwrlWYmOnfoP3YQ6fpAguaxp9L7LvNr7lMzgpFpAg/yQh9rAmQr2yD7epodUNo6x7GQo3btbfVH1I55zIFa+QL7uQK12jVPEwDMYSml0RH6UY5N0zpM3mDaFZgWZwEOKnCMdOIMrfqf/GINBYVL4jIHwBJx7DGzoCF/mKou5DtRPBl7QKnjKU9ylX9/k/7eCdzMDNFgjM5giRsIPLNbjVBu/kBI6KNwg9PCJ8v0aQNMqRjX86xzmdum8wRGR1/7KHe0Jlt2N9iqCy/bhHuNdARFdQ0P1IlrwQamnwpAbb0bax1SFtN/BZA4jL1X8JZSKUd0+Qnwik+B9F0p7nAyLcnyilj6EXTuB3/QFn6JROtc4OQSH76MMDAiQX4VfCxCG61NsoqcL4QReyIYO/ieOC9tt/u0CIUk4cIuQpVXe39zblvWzE5RLuhg5P00YVgY6BkGVCmFj4rEg4Tsm4rBRxXlRxrETgMjR4zRqcJCM/7aG4eonwcMpuLY27MYftvUS1LaJAaarSRxTUI2SVT8jIn9hYlT9jNgphu4rhkTC0OHzNphHdbBHfy2YfYdDqIFFXoLbTVE4pSjEGb1ZkuJA8uFL8COSj8GQEKPUk0i0VmbaKuJFAYDTcC3u9/u8eCtsd+PUjRLrurunCCOgZhBolfBG8+K7wCDfK8GkZuHoWImTDk4wij89sz21/6S2hLZskO5Bn1l+lBUz7HlhtL6L8e6jK31hMeHQbV3CWMiyr5MFWIVX8ltBeUJ6pp/llJoKu6YZp+qCmqCoKP2D1gzCqVzgv51hUP33+hzBhL9gGNHflJcwnAoZDEUnlK0rFC3oWZFg9Ds6b/E9C/CRke+iqHYRNgmZ/I7HahBfJMAbmFVr1a4jBd0jFP9P15kdDP8NZXt0Tvvrsg2G17NQ0JtCXK+ild9dKUGsiu2j9iWtwqge5lgy5wsPdMV8PMnmA/YMg1bdDqVQQoitcHAwQpRKKvWA6RXg4hji5ha/VAdfpQRhPwY8mbI3ZTMbkM4JgWVRRFgK5PD1SFOqUFifDIQytipqm73FTYWgZdUgCRaokYNYM+qbt1w42jaqBW/Kfjkb0ru7g2NGp2k1v1yiiKKRFEtGZckAC/DAGJ73HTnpK+ZGE6Fx5XY+Rbagnoj+0Xt/pV0KtWYHPDCLcjzIjG+F+BNcVL07VM/yTOIWLbu+w9Wadxv56EJ1eZ09IEf4LOfQ7kbMKyYEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 15 14 24 47&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-15-14-24-47-3f55173bc46d819a3a1fd9affd65c62f-04139.png&quot; data-srcset=&quot;/static/2024-11-15-14-24-47-3f55173bc46d819a3a1fd9affd65c62f-53fb7.png 200w,
/static/2024-11-15-14-24-47-3f55173bc46d819a3a1fd9affd65c62f-e3470.png 400w,
/static/2024-11-15-14-24-47-3f55173bc46d819a3a1fd9affd65c62f-04139.png 414w&quot; data-sizes=&quot;(max-width: 414px) 100vw, 414px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中，DispatcherHander 的 handler 方法用来匹配不同的 HandlerMapping 并处理请求，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;59734504710028080000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Mono&lt;Void&gt; handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }

   return Flux.fromIterable(this.handlerMappings)
      .concatMap(mapping -&gt; mapping.getHandler(exchange))
      .next()
      .switchIfEmpty(createNotFoundError())
      // 这里根据获取到的 handler 进行处理
      .flatMap(handler -&gt; invokeHandler(exchange, handler))
      .flatMap(result -&gt; handleResult(exchange, result));
}`, `59734504710028080000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerMappings &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createNotFoundError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Flux&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromIterable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handlerMappings&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapping &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; mapping&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;switchIfEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createNotFoundError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 这里根据获取到的 handler 进行处理&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handler &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这时候，DispatcherHander 就能在一组 handlerMappings 找到 RoutePredicateHandlerMapping，而这个 RoutePredicateHandlerMapping 也是在自动配置类 GatewayAutoConfiguration 中进行装配的，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31359970049826070000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GatewayAutoConfiguration {
   @Bean
   public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
      return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
   }
}`, `31359970049826070000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GatewayAutoConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RoutePredicateHandlerMapping&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;routePredicateHandlerMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FilteringWebHandler&lt;/span&gt; webHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RouteLocator&lt;/span&gt; routeLocator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GlobalCorsProperties&lt;/span&gt; globalCorsProperties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RoutePredicateHandlerMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routeLocator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; globalCorsProperties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然在 DispatcherHander 的 handler 方法中，使用的是响应式编程的代码语法，但整个流程和 Spring MVC 中的处理机制非常类似。而在 RoutePredicateHandlerMapping 中，通过 getHandlerInternal 方法获取了当前请求的路由信息并放入了上下文中。注意到在这个方法中存在一个 lookupRoute 方法，该方法用来查找真正的路由信息，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19679540586624910000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected Mono&lt;Route&gt; lookupRoute(ServerWebExchange exchange) {
   return this.routeLocator.getRoutes()
      .concatMap(
         route -&gt; Mono.just(route).filterWhen(r -&gt; {
            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
            // 根据断言获取到真正的 Route
            return r.getPredicate().apply(exchange);
         })
         .doOnError()
         .onErrorResume(e -&gt; Mono.empty())
      )
      .next()
      .map(route -&gt; {
         // ...
         // 自定义的路由校验方法入口
         validateRoute(route, exchange);
         return route;
      });
}`, `19679540586624910000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lookupRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routeLocator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRoutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
         route &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;just&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;route&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filterWhen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GATEWAY_PREDICATE_ROUTE_ATTR&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 根据断言获取到真正的 Route&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPredicate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;doOnError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onErrorResume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;route &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 自定义的路由校验方法入口&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;validateRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;route&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; route&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，这里通过断言来获取路由信息。一旦获取路由信息之后，下一步就是加载各种过滤器了，这时候就来到了 FilteringWebHandler。该类实现了 WebHandler 接口，并在它的构造函数中通过一个 loadFilters 方法加载过滤器，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15545065302285410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class FilteringWebHandler implements WebHandler {
   protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
   private final List&lt;GatewayFilter&gt; globalFilters;

   public FilteringWebHandler(List&lt;GlobalFilter&gt; globalFilters) {
      this.globalFilters = loadFilters(globalFilters);
   }

   // 将传入的 GlobalFilter 转化为 GatewayFilter
   private static List&lt;GatewayFilter&gt; loadFilters(List&lt;GlobalFilter&gt; filters) {
      return filters.stream().map(filter -&gt; {
         GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
         if (filter instanceof Ordered) {
            int order = ((Ordered) filter).getOrder();
            return new OrderedGatewayFilter(gatewayFilter, order);
         }
         return gatewayFilter;
      }).collect(Collectors.toList());
   }

   // …
}`, `15545065302285410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilteringWebHandler&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WebHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Log&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FilteringWebHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GatewayFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; globalFilters&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilteringWebHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; globalFilters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;globalFilters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadFilters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;globalFilters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 将传入的 GlobalFilter 转化为 GatewayFilter&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GatewayFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadFilters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;GatewayFilterAdapter&lt;/span&gt; gatewayFilter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GatewayFilterAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ordered&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ordered&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderedGatewayFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gatewayFilter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; gatewayFilter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，上述 loadFilters 方法的作用就是将传入的 GlobalFilter 转化为 GatewayFilter，关于这两类过滤器我们后面还会介绍。&lt;/p&gt;
&lt;p&gt;我们接着来看 FilteringWebHandler 的 handle 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;56160758178812260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Mono&lt;Void&gt; handle(ServerWebExchange exchange) {
   // 从 ServerWebExchange 获取 Route
   Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
   // 从 Route 获取到 Filter
   List&lt;GatewayFilter&gt; gatewayFilters = route.getFilters();

   // 将 GlobalFilter 和路由的 Filter 合并
   List&lt;GatewayFilter&gt; combined = new ArrayList&lt;&gt;(this.globalFilters);
   combined.addAll(gatewayFilters);
   // 对 Filter 排序
   AnnotationAwareOrderComparator.sort(combined);

   // 创建 FilterChain 并执行过滤
   return new DefaultGatewayFilterChain(combined).filter(exchange);
}`, `56160758178812260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 从 ServerWebExchange 获取 Route&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Route&lt;/span&gt; route &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRequiredAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GATEWAY_ROUTE_ATTR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 从 Route 获取到 Filter&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GatewayFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; gatewayFilters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; route&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 将 GlobalFilter 和路由的 Filter 合并&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GatewayFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; combined &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;globalFilters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   combined&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gatewayFilters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 对 Filter 排序&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;AnnotationAwareOrderComparator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;combined&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 创建 FilterChain 并执行过滤&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultGatewayFilterChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;combined&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述方法中的几个步骤都很明确，而这里出现的 DefaultGatewayFilterChain 就是一种过滤器链，用来基于该链中的各个过滤器执行过滤操作，具体如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;46614838469915030000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Mono&lt;Void&gt; filter(ServerWebExchange exchange) {
   return Mono.defer(() -&gt; {
      if (this.index &lt; filters.size()) {
         GatewayFilter filter = filters.get(this.index);
         DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
         return filter.filter(exchange, chain);
      } else {
         return Mono.empty();
      }
   });
}`, `46614838469915030000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;GatewayFilter&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;DefaultGatewayFilterChain&lt;/span&gt; chain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultGatewayFilterChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，DefaultGatewayFilterChain 是一种典型的管道过滤器架构模式的应用方式，关于这一架构模式我们在后面还会有详细的讨论。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Gateway 中内置了许多过滤器，这些过滤器可以分成两大类，即 GatewayFilter 和 GlobalFilter，正如我们在前面的 FilteringWebHandler 中所看到的。其中，GatewayFilter 通过配置作用于每次路由，而 GlobalFilter 则作用于所有的请求。当一个请求被匹配到对应路由时，会将 GlobalFilter 和已绑定路由的 GatewayFilter 合并到一起。所有的过滤器都实现了 &lt;code class=&quot;language-text&quot;&gt;org.springframework.core.Ordered&lt;/code&gt; 接口，因此会根据过滤器的 Order 值进行排序。&lt;/p&gt;
&lt;p&gt;在这里，我们无意对所有的过滤器做一一展开，而是挑选其中具有代表性的一个过滤器，并对它的实现过程进行分析，从而帮助你更好地理解过滤器的具体运行原理。&lt;/p&gt;
&lt;p&gt;这里我们以 RouteToRequestUrlFilter 为例来分析过滤器的实现机制，该过滤器用于根据 RouteUri 生成请求的真正 URL，并放入请求上下文中供后续 Filter 使用。RouteToRequestUrlFilter 的 filter 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;60885777765523660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
   if (route == null) {
      return chain.filter(exchange);
   }

   // 获取请求的 URI
   URI uri = exchange.getRequest().getURI();
   // 判断是否包含诸如 % 等的编码部分内容
   boolean encoded = containsEncodedParts(uri);
   // 获取 Route 的 uri
   URI routeUri = route.getUri();
   // 判断是否为其他类型的协议，如果是则根据特定协议生成 URI
   if (hasAnotherScheme(routeUri)) {
      // 将当前请求的 schema 放入上下文中
      exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
      routeUri = URI.create(routeUri.getSchemeSpecificPart());
   }

   // 如果 RouteUri 以 lb 开头，则请求中必须带有 host，否则直接抛出异常
   if (&amp;quot;lb&amp;quot;.equalsIgnoreCase(routeUri.getScheme()) &amp;&amp; routeUri.getHost() == null) {
      throw new IllegalStateException(&amp;quot;Invalid host: &amp;quot; + routeUri.toString());
   }

   // 生成请求 URL，并放入上下文中
   URI mergedUrl = UriComponentsBuilder
      .fromUri(uri)
      .scheme(routeUri.getScheme())
      .host(routeUri.getHost())
      .port(routeUri.getPort())
      .build(encoded)
      .toUri();
   exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
   return chain.filter(exchange);
}`, `60885777765523660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServerWebExchange&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GatewayFilterChain&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Route&lt;/span&gt; route &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GATEWAY_ROUTE_ATTR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;route &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 获取请求的 URI&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 判断是否包含诸如 % 等的编码部分内容&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; encoded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;containsEncodedParts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取 Route 的 uri&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; routeUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; route&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 判断是否为其他类型的协议，如果是则根据特定协议生成 URI&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasAnotherScheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 将当前请求的 schema 放入上下文中&lt;/span&gt;
      exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GATEWAY_SCHEME_PREFIX_ATTR&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      routeUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; URI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSchemeSpecificPart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果 RouteUri 以 lb 开头，则请求中必须带有 host，否则直接抛出异常&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;lb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Invalid host: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 生成请求 URL，并放入上下文中&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;URI&lt;/span&gt; mergedUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UriComponentsBuilder&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routeUri&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;encoded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   exchange&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GATEWAY_REQUEST_URL_ATTR&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mergedUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们在上述方法的关键代码语句中添加了注释。可以看到，一个典型的过滤器的实现过程基本就是从 ServerWebExchange 上下文中获取路由，然后根据路由中的详细信息来执行对应的操作。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-14&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;对于服务网关而言，面试过程中关于网关基本概念的考查相对比较简单，我们不做展开。&lt;/p&gt;
&lt;p&gt;在准备面试的过程中，建议你熟练掌握至少一种服务网关的具体实现工具，例如本讲中介绍的 Spring Cloud Gateway 就是 Spring 家族自研的网关，内置了 Spring 框架自带的功能特性，非常适合进行系统的学习。和其他服务网关的实现机制类似，Spring Cloud Gateway 也是管道过滤器架构模式的一种典型应用，因此它的整体处理流程势必涉及到过滤器、过滤器链等组件。另一方面，因为 Spring Cloud Gateway 是 Spring 家族中的一员，其处理流程也依赖于 Spring 框架对 Web 请求和响应过程的抽象，这就需要引出 DispatcherHandler、RoutePredicateHandlerMapping 和 FilteringWebHandler 等一系列核心组件。&lt;/p&gt;
&lt;p&gt;从回答思路上讲，我们也可以基于这些组件从两个维度来阐述请求处理流程，一个是 Spring MVC 以及 WebFlux 中对 Web 请求的通用处理流程，另一个就是 Spring Cloud Gateway 内置的路由和过滤器维度。&lt;/p&gt;
&lt;p&gt;在本讲中，限于篇幅，我们没有对 Spring Cloud Gateway 所提供的限流等功能进行展开。但从面试点角度讲，对于这些具体功能特性的考查会比较综合，难度也比较大。&lt;/p&gt;
&lt;p&gt;一方面需要面试者对 Spring Cloud Gateway 作为服务网关的一些基本功能进行阐述，另一方面也需要面试者理解分布式环境下限流的概念和常见实现机制，以及 Spring Cloud Gateway 中的具体做法。以限流为例，因为考查的知识点比较多，所以我们重点把握几点。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我们明确 Spring Cloud Gateway 中实现限流的工具是过滤器，而且是一种 GatewayFilter。&lt;/li&gt;
&lt;li&gt;我们明确这个过滤器实现限流时采用的是令牌桶算法。&lt;/li&gt;
&lt;li&gt;Spring Cloud Gateway 实现令牌桶算法借助的工具是 Redis。&lt;/li&gt;
&lt;li&gt;Redis 是通过一系列 Rua 脚本来完成令牌的申请和释放。&lt;/li&gt;
&lt;li&gt;我们需要明确通过 Spring Cloud Gateway 实现限流的做法是使用配置项，可以把重点的配置项做展开。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样，面试过程中涉及到的知识点就都已经介绍到了。关于这些特定的功能特性，你需要在本讲内容的基础上自己做进一步的学习和提升。&lt;/p&gt;
&lt;p&gt;作为扩展类话题，我们也可以对响应式编程做一些展开。响应式编程虽然已经出来有几年了，但在业务系统开发过程中应用并不广泛，目前主要还是为了满足高性能等技术需求，像 Spring Cloud Gateway 这种网关类消息中间件就是其最好的应用场景之一。虽然是一种新技术，但响应式编程也不是一种完全颠覆式的技术体系，而是在现有的异步调用、观察者模式、发布-订阅模式等的基础上发展起来的一种编程模式，能够为系统带来即时响应性。&lt;/p&gt;
&lt;p&gt;虽然，在本讲内容中，我们没有对响应式编程进行过多的展开，但对于技术原理型面试而言，对核心的新技术进行阐述无疑也是面试过程中的一个亮点。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-12&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本章内容围绕服务网关展开讨论。在分布式架构中，服务网关的出现有其必然性。而以 Spring Cloud Gateway 为代表的实现技术在提供高性能的同时也丰富了作为服务网关的核心功能。我们重点对 Spring Cloud Gateway 中的基本架构、服务路由以及过滤器机制进行了详细的探讨。&lt;/p&gt;
&lt;p&gt;介绍完服务网关，我们下一个要引入的技术组件是配置中心。作为一个独立的服务端组件，配置中心需要完成与各个服务之间的交互，并统一管理这些服务中所有的配置信息。那么，配置中心和各个服务之间的这种交互过程是如何实现的呢？下一讲我们将围绕这个话题展开讨论。&lt;/p&gt;
&lt;h1 id=&quot;配置中心：配置中心和各个服务之间是如何交互的？&quot;&gt;&lt;a href=&quot;#%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%EF%BC%9A%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E5%92%8C%E5%90%84%E4%B8%AA%E6%9C%8D%E5%8A%A1%E4%B9%8B%E9%97%B4%E6%98%AF%E5%A6%82%E4%BD%95%E4%BA%A4%E4%BA%92%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;配置中心：配置中心和各个服务之间是如何交互的？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们详细讨论了服务网关这一分布式系统构建过程中必不可少的技术组件。我们知道随着服务数量的增多，在客户端和服务端之间应该架设一层网关来确保对请求进行路由和转发。事实上，针对配置信息的有效管理也是系统开发过程中的基本要求，这点对于分布式系统而言尤为明显。&lt;/p&gt;
&lt;p&gt;为了更好地管理系统中的各种配置信息，我们需要引入一个新的技术组件，即配置中心。和服务网关一样，配置中心同样也是一个独立的服务器组件。&lt;/p&gt;
&lt;p&gt;那么，问题就来了，原本分散在各个服务中的配置信息如何集中到配置中心进行统一管理呢？配置中心和各个服务之间又是如何交互的呢？在本讲内容中，我们将围绕这些问题展开讨论，并提供对应的面试题和开源框架原理分析。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-12&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-12&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;试想一下，在一个分布式系统中，势必存在多个服务，而这些服务一般都会构建开发、测试和生产等多套环境，每套环境都是一个集群。而针对不同的环境，我们都会采用一套不同的配置体系。那么如何保证多个环境中的这些配置信息都能在各个服务实例中进行实时的同步更新呢？&lt;/p&gt;
&lt;p&gt;显然，把配置信息分散在各个服务中交由它们自己管理是不合适的，我们需要引入一个集中式管理的工具。这个工具就是分布式系统中所必备的一个技术组件，即配置中心。有了配置中心之后，所有分散在各个服务、各个环境中的配置信息都将被收拢到配置中心进行统一管理，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-10-25-59-5020406204eef1d0f9c0fc346c71f0e2-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.749226006191954%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB7ElEQVQoz22Sa08aURCG9///iH5qopRIEVCuIqhRqCKXBffCHXe5GQR2Fygs8PTs1ia16SRPJmfeM5Mzc0aKv6S5GubJGHlSg2sy5g2xZpJg+TsRNU7ayAk8PUfW07opFFPDs8PxwPF4/IR0WwpQaAS4q55QaZ+TLX3ltnZCTvh7OUBRPfNjxUZQ3DnlhxpAVgtsf7ofRY+fkGbjLuO+wkjwPmozaMnMRi3mk47Pm6kzaMtMXjVGPYU3Q2Ntz3FWaxzH4V+TOjODztLDpDV/pWcP0d56vIjinm8vDLqW+eGFNuszX1l+8sKyeNR1Wu/vaNMp6niMFC1/I/saI90R7Q5iJLsRwqLl04cvRMUoMv0oSaFd9SJcCS3VDfGkFXE3OzqmQdgwiE0mpBYLblwXSW2kqVciVEvndJsZivdBlPqlj64kUBqXPNwFfP2xcEajeo5p/P6UzqBPXNe4FkXzwyG30wmSs16xdCwswfIDa2Vjbxzste3HF/YfzWZuLXD3ezabDcvlEne3Yy9e5ro7dtst0uVLgutpnuwoJ9ZHrI3goh0nJIeJ6hf+2cPTc+MbEkaK51aZ7XrL/0x6lqNUlDBPcohmP8lDJcijfEbhOUCpHqKqRymIWE2N8FQLUW+K0QxkP/nv/TscDj6/AEiTg9tWhOlLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 10 25 59&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-10-25-59-5020406204eef1d0f9c0fc346c71f0e2-0e173.png&quot; data-srcset=&quot;/static/2024-11-18-10-25-59-5020406204eef1d0f9c0fc346c71f0e2-2fb9f.png 200w,
/static/2024-11-18-10-25-59-5020406204eef1d0f9c0fc346c71f0e2-f1e72.png 400w,
/static/2024-11-18-10-25-59-5020406204eef1d0f9c0fc346c71f0e2-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;针对上图，实际上我们已经可以梳理一组在日常面试过程中经常出现的话题，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置中心如何区分来自不同环境的配置信息，并自动进行隔离？&lt;/li&gt;
&lt;li&gt;配置中心采用什么样的存储方式来保存配置信息？&lt;/li&gt;
&lt;li&gt;配置中心的基本组成结构是怎么样的？&lt;/li&gt;
&lt;li&gt;各个服务如何从配置中心中获取最新的配置信息？&lt;/li&gt;
&lt;li&gt;如果让你来设计一款配置中心，你会怎么做？&lt;/li&gt;
&lt;li&gt;你知道 Spring Cloud Config 中客户端和服务端之前的交互流程是怎么样的吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述问题体现的都是与配置中心相关的、比较常见的一些考查方式。就考查内容上而言，也主要包括两个维度，一个是理论知识，一个是具体工具框架的应用实践。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-13&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;关于系统中配置信息的管理，常见的做法是把它们存放在配置文件中。在单块系统中，普通的配置文件能够满足需求，围绕配置文件展开的配置管理工作通常不会有太大挑战。但在分布式系统中，配置文件的管理就会暴露出一些问题。针对这类问题，我们首先需要梳理配置中心的概念、在分布式系统中的定位，以及配置信息的分类和配置管理的需求等。&lt;/p&gt;
&lt;p&gt;基于配置中心的实现需求，业界存在一批典型的分布式配置中心实现工具，常见的包括 Etcd、Consul、Disconf、Diamond、Nacos 以及 Spring Cloud Config。显然，不同的工具具有不同的设计原理和实现机制。在面试过程中，候选人可以挑选自己最擅长的一个工具展开具体的讨论。&lt;/p&gt;
&lt;p&gt;最后，也是最重要的，就是配置中心与服务之间的交互关系。以我的经验，但凡涉及到配置中心的考查，这个问题是大概率要碰到的。任何一款配置中心实现框架都提供了一整套完善的集成机制，确保分布式系统中的各个服务能够快速高效地访问配置中心中的配置信息。而且，这个过程对于开发者而言通常是透明的，这就导致我们对这部分工作机制缺乏必要的认知，面试结果上也往往回答得不理想。&lt;/p&gt;
&lt;p&gt;讲到这里，你可能会问，配置中心到底长得怎么样呢？在接下来的内容中，我们先来梳理配置中心的基本模型。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-15&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;每一个分布式系统都应该有一个配置中心，下图展示了配置中心的定位以及它所应具备的基本结构：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-15-00-22-d57137a41b3e185718aa5ad5d02b063f-2b2cd.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 513px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 84.40545808966861%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAACmUlEQVQ4y5WU21PaUBCH+f8f2j50OtMne3no0FLrDRAooBISLnIJISSBgBhANEFEEkQk4ddNsF5KtfXM7Oyey37Zs3s2vsXCwaos4DiAKLKo1TaRzwc8qQobKBZ24Q73nOOs+vrwxJjNbNQVFuaYQbn0DXwpAP0sAVn6CdtePOUGn6pKqNcraDZFNNUqJKmI83PD2xyNRjhqNWm9QZGKaLePoOtn3p5lmgQvk1/Vk3qdh6qK8FX4AC5HSbqOH8ftHcymDKpVlsK//+p8bsO0rEeRuJCBHkO7tUXgAIaDOAx9Dz6ePyBYEtlMmPITI70NTVMfOdvzOSYU0cMxHBrg2C3KbRiH+QiEShyKzK7mcDqdodc7Qb3VQkc3SHQ0NA2iqqJjGNBortL8mMSyrlZz6FZ0KQ5sx/YWG90uXofDeBvawVeBx7tIGK9+fIdfKON9PIY34RAytZp39r7SS44HxO3E3XSH2OkhwO1iMmYxmRZgTnIYm1lcXRdhXaaRqsbAiPISeJvsO+DvUB8C+ZaGTS7oWmg1Q550O1GqeAiLmxzKrT0kivyDCO8r+Fdg5UjDOrNJVgkXgySGegLmcN/TmGdRUGJIlIT/BypUFH8pC07lwMgM0gpDOgWumfHsoHSIjFK/u/K/gZ0Odi9GSE6usVYW8UFQ8KVxjHXtFAlrihR1Sk5RXggcGCiQHVUKiOSCCHPbiMt5sLMZWNtGVpZfBowMz5GmDskLQXSpG/ReBJ2TPQT7p8jQmT+Bz1Z5GeEAzPUNyvUY+t0o+j3SBocdyi+HxcuAMgHj5hh5NxIpjv4J9alxAKlNz8W0cEjrGfnxO3z2yioBP+Wy2KJ2+8ymsZaIevIxtY8NKoZfEFCQlkDbWf4Xbcqrq38BFcXyFmRmkf0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 15 00 22&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-15-00-22-d57137a41b3e185718aa5ad5d02b063f-2b2cd.png&quot; data-srcset=&quot;/static/2024-11-18-15-00-22-d57137a41b3e185718aa5ad5d02b063f-2a6c9.png 200w,
/static/2024-11-18-15-00-22-d57137a41b3e185718aa5ad5d02b063f-aed38.png 400w,
/static/2024-11-18-15-00-22-d57137a41b3e185718aa5ad5d02b063f-2b2cd.png 513w&quot; data-sizes=&quot;(max-width: 513px) 100vw, 513px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们看到配置中心由两个核心组件构成，分别是配置仓库和配置服务器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;配置仓库&lt;/p&gt;
&lt;p&gt;配置中心中的所有配置信息都存放在一个配置仓库（Repository）中。配置仓库的实现方式有很多，我们可以采用 SVN 和 Gitlab 等具备版本控制功能的第三方工具，也可以自建一个具有持久化或内存存储功能的存储媒介。通常，开发人员通过配置管理和持续交付的各项模式和实践，并借助于特定工具完成配置仓库中配置信息的更新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置服务器&lt;/p&gt;
&lt;p&gt;配置服务器封装对配置仓库的相关操作，充当配置仓库的管理者角色。和系统中的其他服务一样，它在物理上也是一个能够独立运行的服务，提供了对外的访问入口。当需要使用配置信息的服务启动时，这些服务就会使用这些访问入口获取存储在配置仓库中的各种配置信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;显然，配置服务器需要屏蔽不同配置仓库实现在技术上的差异；另一方面，配置服务器也需要提供一种通知机制，确保配置仓库中的配置信息变化之后能够告知各个服务。关于这个问题实现起来比较复杂，我们放在下一讲中做专题讲解。&lt;/p&gt;
&lt;p&gt;介绍完配置中心的基本结构之后，我们接下来讨论作为一个配置中心所应具备的核心功能，包括隔离性、一致性和安全性。&lt;/p&gt;
&lt;p&gt;针对配置信息的管理，做到隔离性是最基本的要求。所谓隔离性，指的就是特定配置项只能用于特定环境中，不同环境中的配置项不应该相互混淆，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-15-01-18-6a929c4f9e87438e58e1106a5ae7bbc7-8d399.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 516px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.44186046511628%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABxklEQVQoz1VRaW/aUBD0//8LyQdyEHO5QAKNKtooKkEQ4+v5wNglxm5jICEhNodIAmK6NkJq39Nodp5Ga+8OV+5courWkZNLyMsCclIJFaeGq/s6nifP+Nx8YvPxgWtFhmBZKKoqCopCPgm5bjety70emNPHbrsFV2FXuF3coWRXUCTkTQE3cRNfpCpc28ViuUD0+oqarqPx9gbBccjbR4Gan0kycQ8/4hhtxjAOQ3CzlxkCP8CIRPgYEo/gez7m8Rz/nnkUkc8nz2OKgz+pA2+494zH4Nr2PVrDNn66LTQJCSdadCSslivs6GK3gzIYoEloJey6aDrungl39ObTB16ensDxShHX8Q2OxAyy/SIyLIvG6hbnbR5MYYjiCNPJBALtrjqdIqNpuKCxT00Tx7KMrG2jsV4jV6vtRw7CAIZrwnQtWL96KXTHQDgJ/xv5z2gExTCgUiOWhEA4aI32uVguUx9nDi1ovxm0gOAzsEBPte3beF+/U8obbAkG7UkcDiHRHkXPQ5fqAxI9pr9PG/LdAr7OvuPczIOnkY/Fk1SftXjoqo45pTwjc5lGrlPapQcPxcEDTlQNR50OTindbxSgSGkn5y+hGzEmMiIGHwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 15 01 18&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-15-01-18-6a929c4f9e87438e58e1106a5ae7bbc7-8d399.png&quot; data-srcset=&quot;/static/2024-11-18-15-01-18-6a929c4f9e87438e58e1106a5ae7bbc7-8ba40.png 200w,
/static/2024-11-18-15-01-18-6a929c4f9e87438e58e1106a5ae7bbc7-7e253.png 400w,
/static/2024-11-18-15-01-18-6a929c4f9e87438e58e1106a5ae7bbc7-8d399.png 516w&quot; data-sizes=&quot;(max-width: 516px) 100vw, 516px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;一致性的讨论对象是集群环境，即在一个集群环境中的所有服务都应该使用同一份完全相同的配置信息，针对配置信息的操作结果对于这些服务而言应该是完全一致的，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-15-01-44-d61ee364ea5398bbb23523be3056a5cd-ab538.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 564px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.297872340425535%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABnElEQVQoz3WOa3PSUBRF8/9/gx/saOsIBVugNuGRUl4DCHRsEyAtMRAEJdCKthaB5C5vYutjxp6ZNWf23ufce5SUo3I4OP6DfUzaVsk4GrvtOM9Od3he2+VlK0ZK+ukHUg/zv7v0DnpHKKXPeU7GWYqTXITuamRdlexY49BIEG+9InH2mpSRRP+YlZmGNlIpyFwP56V3InuoVectyt3ylO5FDPdDRvY4wbrJWXuP1rsX/Phew+olmE1z9M197m8rNOo7mHJueV3EMhMM7TQ9Y5/bZSnKlfP3Oo2GRqdToFJOUy4fUJI99Kx+hWZDJZ+P0W7lIl2vq1QrGQrSq1Yz0s/TbGpcnBfp92oo63WA7xOx3vgsFks8b8FqtSEsIbGsAV+/3UX6frVmNlvIuS9styLC9wWbTSARKPynAn/L4NJicHXJYu7Jy3qMRyNsqUPfl/lTpQgheCQQIjK7jkPBm6G5LknTJHM14Mi2yU2nZCcTzOHw18dBwN/7If9cKB4flAv6fE7R84h1OrwxTJKGQeXmhtynKV157VMP/gS9qjrAKzmGuwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 15 01 44&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-15-01-44-d61ee364ea5398bbb23523be3056a5cd-ab538.png&quot; data-srcset=&quot;/static/2024-11-18-15-01-44-d61ee364ea5398bbb23523be3056a5cd-4fcfe.png 200w,
/static/2024-11-18-15-01-44-d61ee364ea5398bbb23523be3056a5cd-9c055.png 400w,
/static/2024-11-18-15-01-44-d61ee364ea5398bbb23523be3056a5cd-ab538.png 564w&quot; data-sizes=&quot;(max-width: 564px) 100vw, 564px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;最后的安全性比较容易理解，即只有在通过合法的授权之后，开发人员才能访问配置中心中的关键配置内容，从而避免敏感信息的泄露。我们需要讨论两个维度，一个维度是从配置服务器的角度出发确保位于服务器上的配置信息不被随意访问，另一个维度则是从数据传输的角度出发确保敏感配置信息得到应有的保护。&lt;/p&gt;
&lt;p&gt;在接下来的内容中，我们继续介绍 Spring Cloud 框架，并选择 Spring 家族自研的 Spring Cloud Config 配置中心展开讨论。Spring Cloud Config 使用起来非常简洁，也便于管理，对于所有的配置数据都开放了一套完整的 RESTful API，任何系统都可以基于 HTTP 协议来访问配置仓库中的配置数据，从而构建满足多样化需求的管理界面。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-13&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在 Spring Cloud Config 中，整体结构分成非常清晰的两部分，即代表服务器端组件的 Spring Cloud Config Server 和代表客户端组件的 Spring Cloud Config Client，我们先从服务器端开始讲起。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-config-server-工作机制&quot;&gt;&lt;a href=&quot;#spring-cloud-config-server-%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Config Server 工作机制&lt;/h3&gt;
&lt;p&gt;对于 Spring Cloud Config 而言，它把所有的配置信息抽象为一种 Environment（环境），而存储这些配置信息的地方就称为 EnvironmentRepository。&lt;/p&gt;
&lt;p&gt;EnvironmentRepository 就是最常用的基于配置仓库的配置中心实现方案的具体体现。EnvironmentRepository 接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50442450212948730000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface EnvironmentRepository {
   Environment findOne(String application, String profile, String label);
}`, `50442450212948730000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnvironmentRepository&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这个接口非常简单，基于上述 findOne 方法定义以及对配置中心基本模型的了解，我们发现 Spring Cloud Config 中把配置信息抽象为如下三个维度进行管理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用 (application)&lt;/li&gt;
&lt;li&gt;环境 (profile)&lt;/li&gt;
&lt;li&gt;版本 (label)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这三个维度，我们就可以确定一份唯一的配置数据。&lt;/p&gt;
&lt;p&gt;EnvironmentRepository 的实现类非常多，那么我们选择哪一个 EnvironmentRepository 来作为切入点呢？这个问题实际上不难回答，因为 Spring Cloud Config 为我们提供了一个默认的 EnvironmentRepositoryConfiguration，即 DefaultRepositoryConfiguration，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;76402582100956360000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
   @Autowired
   private ConfigurableEnvironment environment;

   @Autowired
   private ConfigServerProperties server;

   @Autowired(required = false)
   private TransportConfigCallback customTransportConfigCallback;

   @Bean
   public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,  MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
      return gitEnvironmentRepositoryFactory.build(environmentProperties);
   }
}`, `76402582100956360000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnvironmentRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SearchStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CURRENT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultRepositoryConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigurableEnvironment&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigServerProperties&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;required &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransportConfigCallback&lt;/span&gt; customTransportConfigCallback&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MultipleJGitEnvironmentRepository&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defaultEnvironmentRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MultipleJGitEnvironmentRepositoryFactory&lt;/span&gt; gitEnvironmentRepositoryFactory&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token class-name&quot;&gt;MultipleJGitEnvironmentProperties&lt;/span&gt; environmentProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; gitEnvironmentRepositoryFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;environmentProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里出现了一个 MultipleJGitEnvironmentRepository，而 MultipleJGitEnvironmentRepository 则继承了抽象类 JGitEnvironmentRepository。不难看出，Spring Cloud Config 底层默认使用的 EnvironmentRepository 就是我们所熟悉的 Git。&lt;/p&gt;
&lt;p&gt;在 JGitEnvironmentRepository 抽象类中，提供了大量针对第三方 Git 仓库的操作代码，这些都不是理解配置中心的重点内容，这里不做展开。我们只需要明白，无论采用 Git、SVN 等具体哪一种配置仓库的实现方式，最终我们处理的对象都是位于本地文件系统中的配置文件。为了理解这点，我们需要围绕 MultipleJGitEnvironmentRepository 类从底向上回顾整个类层结构，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-15-03-26-3fdf77af5f02641eaabf0eb1c9b966e2-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.21329211746522%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABmElEQVQoz3VSa0/CQBDs///sB2KMGk1AG0384jNieIo8W1paUKRQaIEIFQoaAu0xbg+NinjJZHfvdrYzmwrYcDyPoarHUMxHUMiLKOSOIUsnKBZEZB/oLhdBWb7YRIXgeT4CMLbkCPLZbIH6Uw5a5Qq1ahRPtTuoyiXPq/otv9e1BBYLBt9nWC6XXMR87kEwW1l07SyU8g01RfHSz8NsSZhM3qnZ5wTGGBznlUgerwMMhw715WA0kpBK1zw6QwmCVDoiNaeoqEdkI0xqRBp8Q1/3f1lZr8fjMXHO8FgVocgR1HQRtnUOoWnIpLCCblfj0bYUNI0KptN3DAcdmKYK266RAhltU0Ono6HXMzAajWAYZfS6K65tqbAIAv455JKv4S66hfvULlLJHcRj20jEQ8jcn/xHgxAs9CfAI+OPb28uXLdP+3whZXW4Y8qD2nX4e9C3zv8zkA+lMxhYaDwX0G6rtPwyWS6RZYXXbVNGv9/6HPrN4Qr/Wl2pe64XEb0NIZs5RDq5T/9jGA+UZ9IHZHkPup7+NfALHxPKmvtOuzbBAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 15 03 26&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-15-03-26-3fdf77af5f02641eaabf0eb1c9b966e2-a40ca.png&quot; data-srcset=&quot;/static/2024-11-18-15-03-26-3fdf77af5f02641eaabf0eb1c9b966e2-3426a.png 200w,
/static/2024-11-18-15-03-26-3fdf77af5f02641eaabf0eb1c9b966e2-f3a4f.png 400w,
/static/2024-11-18-15-03-26-3fdf77af5f02641eaabf0eb1c9b966e2-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从类的命名和职责上讲，AbstractScmEnvironmentRepository 是 JGitEnvironmentRepository 的父类，其主要目的是建立能够在本地进行配置信息管理所需要的文件环境，它的 findOne 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45571091863316500000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public synchronized Environment findOne(String application, String profile, String label) {
   NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(), new NativeEnvironmentProperties());
   Locations locations = getLocations(application, profile, label);
   delegate.setSearchLocations(locations.getLocations());
   Environment result = delegate.findOne(application, profile, &amp;quot;&amp;quot;);
   result.setVersion(locations.getVersion());
   result.setLabel(label);
   return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),getUri());
}`, `45571091863316500000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; application&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;NativeEnvironmentRepository&lt;/span&gt; delegate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NativeEnvironmentRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NativeEnvironmentProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Locations&lt;/span&gt; locations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getLocations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   delegate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSearchLocations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLocations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cleaner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getWorkingDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toURI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里出现了一个 NativeEnvironmentRepository，从命名上我们不难看出这个 EnvironmentRepository 操作的对象是本地文件。我们同样关注它的 findOne 方法，如下所示（简单起见做了裁剪）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40438190530799950000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Environment findOne(String config, String profile, String label) {
   SpringApplicationBuilder builder = new SpringApplicationBuilder(PropertyPlaceholderAutoConfiguration.class);
   ConfigurableEnvironment environment = getEnvironment(profile);
   builder.environment(environment);
   builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);

   // 获取配置信息的参数
   String[] args = getArgs(config, profile, label);

   // 设置监听器用于监听配置文件的变化
   builder.application().setListeners(Arrays.asList(new ConfigFileApplicationListener()));
   ConfigurableApplicationContext context = builder.run(args);
   environment.getPropertySources().remove(&amp;quot;profiles&amp;quot;);

   try {
      return clean(new PassthruEnvironmentRepository(environment).findOne(config, profile, label));
   } finally {
      context.close();
   }
}`, `40438190530799950000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;SpringApplicationBuilder&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringApplicationBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PropertyPlaceholderAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ConfigurableEnvironment&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;profile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WebApplicationType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NONE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bannerMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OFF&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 获取配置信息的参数&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getArgs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 设置监听器用于监听配置文件的变化&lt;/span&gt;
   builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setListeners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigFileApplicationListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ConfigurableApplicationContext&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   environment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPropertySources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;profiles&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PassthruEnvironmentRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从代码结构上，我们看到 NativeEnvironmentRepository 最终委托 PassthruEnvironmentRepository 完成了配置文件的读取，然后通过 clean 方法完成本地文件地址与远程仓库之间地址的转换。同时，这里用到了 Spring Boot 自带的 ConfigFileApplicationListener 来监听配置文件的变化。&lt;/p&gt;
&lt;p&gt;讲到这里，我们已经明确了 EnvironmentRepository 的设计和实现过程，接下来我们要讨论如何将配置信息的访问入口暴露给各个服务。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Config 中，通过 EnvironmentRepository 获取的配置信息最终通过 EnvironmentController 暴露给客户端应用程序进行调用，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;67587223230375780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@RestController
@RequestMapping(method = RequestMethod.GET, path = &amp;quot;\${spring.cloud.config.server.prefix:}&amp;quot;)
public class EnvironmentController {
   private EnvironmentRepository repository;
   private ObjectMapper objectMapper;
}`, `67587223230375780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${spring.cloud.config.server.prefix:}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnvironmentController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnvironmentRepository&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectMapper&lt;/span&gt; objectMapper&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到它的关键成员变量有两个，即 EnvironmentRepository 和 ObjectMapper。前者是具体某一个 EnvironmentRepository 的实例，而 ObjectMapper 则用于序列化。&lt;/p&gt;
&lt;p&gt;EnvironmentController 提供了多种获取配置信息的方法，这些方法主要就是前面介绍的 application、profile、label 这三个（或者更少）参数，其中最重要的方法就是如下所示的 defaultLabel 方法和 labelled 方法，这些方法暴露了获取配置的最常用的一组 HTTP 端点。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;99083301121928200000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@RequestMapping(&amp;quot;/{name}/{profiles:.*[^-].*}&amp;quot;)
public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) {
   return labelled(name, profiles, null);
}

@RequestMapping(&amp;quot;/{name}/{profiles}/{label:.*}&amp;quot;)
public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) {
   Environment environment = this.repository.findOne(name, profiles, label);
   if (!acceptEmpty &amp;&amp; (environment == null || environment.getPropertySources().isEmpty())) {
      throw new EnvironmentNotFoundException(&amp;quot;Profile Not found&amp;quot;);
   }
   return environment;
}`, `99083301121928200000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/{name}/{profiles:.*[^-].*}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defaultLabel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;labelled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/{name}/{profiles}/{label:.*}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;labelled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@PathVariable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;acceptEmpty &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;environment &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPropertySources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnvironmentNotFoundException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Profile Not found&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，在 labelled 方法中，会调用 EnvironmentRepository 的 findOne 方法来加载配置，然后返回给配置的消费者，即 Spring Cloud Config 的客户端服务。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-config-client-工作机制&quot;&gt;&lt;a href=&quot;#spring-cloud-config-client-%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Config Client 工作机制&lt;/h3&gt;
&lt;p&gt;使用过 Spring Cloud Config 的同学也都知道，想要在一个服务中集成配置中心，只需在 Spring Boot 的配置文件中添加对服务器地址的引用即可。当然，前提是在类路径中添加对 Spring Cloud Config 的引用。那么，为什么只要添加了引用，就会在服务启动时自动获取远程的配置信息呢？这就是我们接下来需要回答的问题。&lt;/p&gt;
&lt;p&gt;在介绍 Spring Cloud Config 时，我们将采用反推的方法，即从获取服务器端配置信息的入口开始，逐步引出这个问题的答案。&lt;/p&gt;
&lt;p&gt;我们首先找到的是 ConfigServicePropertySourceLocator 类，因为我们在这个类中发现了一个 getRemoteEnvironment 方法。显然，作为客户端组件，Spring Cloud Config Client 的主要职责就是获取服务器端提供的配置项信息。我们已经知道在 Spring Cloud Config Server 中提供了一个 EnvironmentController 来暴露配置信息，那么在客户端中势必存在一个入口来获取这些配置信息。&lt;/p&gt;
&lt;p&gt;这个入口就是 getRemoteEnvironment 方法，如下所示（简单起见做了裁剪）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;67019183467137710000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) {
   // 根据服务器端点的 URL 准备参数
   String path = &amp;quot;/{name}/{profile}&amp;quot;;
   String name = properties.getName();
   String profile = properties.getProfile();
   String token = properties.getToken();
   int noOfUrls = properties.getUri().length;

   // 处理 URL 中的 label
   Object[] args = new String[] { name, profile };
   if (StringUtils.hasText(label)) {
      if (label.contains(&amp;quot;/&amp;quot;)) {
         label = label.replace(&amp;quot;/&amp;quot;, &amp;quot;(_)&amp;quot;);
      }
      args = new String[] { name, profile, label };
      path = path + &amp;quot;/{label}&amp;quot;;
   }
   ResponseEntity&lt;Environment&gt; response = null;

   for (int i = 0; i &lt; noOfUrls; i++) {
      // 准备用于安全访问的 Credentials 信息
      Credentials credentials = properties.getCredentials(i);
      String uri = credentials.getUri();
      String username = credentials.getUsername();
      String password = credentials.getPassword();
      try {
         HttpHeaders headers = new HttpHeaders();
         addAuthorizationToken(properties, headers, username, password);
         if (StringUtils.hasText(token)) {
            headers.add(TOKEN_HEADER, token);
         }
         if (StringUtils.hasText(state) &amp;&amp; properties.isSendState()) {
            headers.add(STATE_HEADER, state);
         }
         headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

         // 通过 RestTemplate 执行远程访问
         final HttpEntity&lt;Void&gt; entity = new HttpEntity&lt;&gt;((Void) null, headers);
         response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
      }
      // 省略 catch 处理和空值校验

      Environment result = response.getBody();
      return result;
   }

   return null;
}`, `67019183467137710000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRemoteEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RestTemplate&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigClientProperties&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 根据服务器端点的 URL 准备参数&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/{name}/{profile}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; profile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; noOfUrls &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 处理 URL 中的 label&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;(_)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/{label}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ResponseEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; noOfUrls&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 准备用于安全访问的 Credentials 信息&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Credentials&lt;/span&gt; credentials &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCredentials&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; credentials&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; credentials&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; credentials&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;addAuthorizationToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TOKEN_HEADER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSendState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;STATE_HEADER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAccept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;singletonList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;APPLICATION_JSON&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// 通过 RestTemplate 执行远程访问&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; entity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; restTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 省略 catch 处理和空值校验&lt;/span&gt;

      &lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码虽然有点长，但我们对照 EnvironmentController 端点的实现方法，很容易理解它的执行流程就是通过 RestTemplate 发起一个 HTTP 请求。请求对象的构建需要获取 application、profile、label 等参数，而请求结果则通过 JSON 反序列化为一个 Environment 对象。我们通过这个 Environment 对象就能获取所有相关的配置数据。&lt;/p&gt;
&lt;p&gt;明白了获取远程配置信息的处理方式，我们来反推 getRemoteEnvironment 方法的触发过程。我们通过代码的调用路径，发现在一个 locate 方法中使用到了这个方法。而讲到这个方法就必须介绍 Spring Cloud 中的一个重要的工具类 PropertySourceLocator。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud 中，PropertySourceLocator 接口定义如下，只包含前面提到的一个 locate 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;77390904229048190000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface PropertySourceLocator {
   PropertySource&lt;?&gt; locate(Environment environment);
}`, `77390904229048190000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PropertySourceLocator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;PropertySource&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;locate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Environment&lt;/span&gt; environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;PropertySourceLocator 接口位于 spring-cloud-context 代码工程中的 org.springframework.cloud.bootstrap.config 包下。当我们看到类的命名以及结合服务启动时自动获取配置信息这一主题应该能够联想到，PropertySourceLocator 肯定被内嵌在一个配置类中，我们在 PropertySourceLocator 接口所在的包结构中找到了 PropertySourceBootstrapConfiguration。这个类实际上非常好找，因为在同一个包结构中一共也只有三个类。&lt;/p&gt;
&lt;p&gt;PropertySourceBootstrapConfiguration 中的实现过程如下所示（代码做了裁剪）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;96984810542514200000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer&lt;ConfigurableApplicationContext&gt;, Ordered {
   private List&lt;PropertySourceLocator&gt; propertySourceLocators = new ArrayList&lt;&gt;();

   public void setPropertySourceLocators(Collection&lt;PropertySourceLocator&gt; propertySourceLocators) {
      this.propertySourceLocators = new ArrayList&lt;&gt;(propertySourceLocators);
   }

   @Override
   public void initialize(ConfigurableApplicationContext applicationContext) {
      // ...
      ConfigurableEnvironment environment = applicationContext.getEnvironment();
      for (PropertySourceLocator locator : this.propertySourceLocators) {
         PropertySource&lt;?&gt; source = null;

         // 调用各个 PropertySourceLocator 的 locate 方法
         source = locator.locate(environment);
         // ...
      }
      // ...
   }
   // 省略了其他变量和方法
}`, `96984810542514200000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PropertySourceBootstrapProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PropertySourceBootstrapConfiguration&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationContextInitializer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigurableApplicationContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ordered&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PropertySourceLocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; propertySourceLocators &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setPropertySourceLocators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PropertySourceLocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; propertySourceLocators&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;propertySourceLocators &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;propertySourceLocators&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigurableApplicationContext&lt;/span&gt; applicationContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;ConfigurableEnvironment&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PropertySourceLocator&lt;/span&gt; locator &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;propertySourceLocators&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;PropertySource&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// 调用各个 PropertySourceLocator 的 locate 方法&lt;/span&gt;
         source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; locator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;locate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 省略了其他变量和方法&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，PropertySourceBootstrapConfiguration 实现了 ApplicationContextInitializer 接口的 initialize 方法。请注意，在 Spring Boot 中，所有 ApplicationContextInitializer 接口的实现类都会在应用程序启动时自动加载。所以上述代码会随着应用启动而运行，通过遍历 propertySourceLocators 的 locate 方法来读取远程服务配置信息。&lt;/p&gt;
&lt;p&gt;在 PropertySourceBootstrapConfiguration 类中，注意到 propertySourceLocators 数组是通过 setPropertySourceLocators 方法直接进行注入的，显然我们需要找到注入 ConfigServicePropertySourceLocator 的入口。正如前文中我们通过 PropertySourceLocator 找到 PropertySourceBootstrapConfiguration 一样，在 ConfigServicePropertySourceLocator 类的同一个包结构中，我们也找到了 ConfigServiceBootstrapConfiguration 配置类，并在该类中发现了如下所示的 configServicePropertySource 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35817906436826360000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Bean
@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = &amp;quot;spring.cloud.config.enabled&amp;quot;, matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
   ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(properties);
   return locator;
}`, `35817906436826360000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigServicePropertySourceLocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.cloud.config.enabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; matchIfMissing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigServicePropertySourceLocator&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configServicePropertySource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigClientProperties&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;ConfigServicePropertySourceLocator&lt;/span&gt; locator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigServicePropertySourceLocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; locator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不难看出，上述方法创建了一个新的 ConfigServicePropertySourceLocator 实例。也就是说当类路径中包含 ConfigServiceBootstrapConfiguration 类时，就会自动实例化一个 ConfigServicePropertySourceLocator。这里就充分应用了 Spring Boot 的自动配置机制，可以通过查看 &lt;code class=&quot;language-text&quot;&gt;META-INF/spring.factories&lt;/code&gt; 中的配置类进行确认，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;3767389489717643000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration`, `3767389489717643000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里的 BootstrapConfiguration 配置项中包含了 &lt;code class=&quot;language-text&quot;&gt;org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration&lt;/code&gt; 类的定义。&lt;/p&gt;
&lt;p&gt;至此，围绕 Spring Cloud Config Client 如何在启动时自动获取 Server 所提供的配置信息的整体流程已经介绍完毕，我们可以通过下图所示的全局蓝图进行梳理和总结：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-18-15-04-05-362804dc3a192baf9f7b9296825c6535-a3ca3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 642px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.85358255451713%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACLUlEQVQoz12Re3eaQBDF/f5fpKdt/khK4yPHNjkxMSrGFyAKwgq+EBsTH8dGIt7e3dM0xy7nwrAzO/Ob2cyV6+LHdIqbKILW6+FTpYLPuo6zZhNf6nWlr9RVEOCacUUZ6/tI0xRyHY/HE2XilxdEqxWmyyWCaI7hbMZvhFEcoz8cKkl7+vyMGWNnLyvMae/3exwOh5NkcmXkSzpuebDMg5emicuOgVy3i2+kPKvVkOWeRjtnWbh4fESRXZUYWw1D4D/SjHzJhJJwwsreZAJ3NII3nUCQdDiPYLOY6Xm058ofMNmYsZEkTRIkb2+nCd9xZbUGZ1Rm8ptwhPNGE1mrC80wcKbXoZG80O+zC4t2BwVJuljgdjzGZrv9aFkhq3wpFpznhAlDUggmlzMVVEi6nhCweSEBbbkX/J2t7C4hqexUEb4R+f3W3te1JCFZgbOUylPyX2u3UbBtZGnnDBNZkuY5W41zvmPBzJzVHMfBhLPZ7XbYEn292aDrC7T6LtrOAMbAh+H56IoADbuHluPCot/0hPJ3XE/JDcfIbHhYJl2v1wo5PaZ43b/iIaygttBRi+soBXfIGwWlc/0CWuM7su0cCuaV8lfnOlVDRVQ/ZvhvsfPk9x5e5MP/JSCWAQaxD2c2QNvroCNMZVuBDXvUg7cQ8Bg3XIUInsKPW5ZPekhhzCzoiwaMpYXHaRP37gPunLL6Fo2fKDn3KA8qaMUGzOcu2k8mOrGJ5DVRPH8A96Uh3H3qooMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 18 15 04 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-18-15-04-05-362804dc3a192baf9f7b9296825c6535-a3ca3.png&quot; data-srcset=&quot;/static/2024-11-18-15-04-05-362804dc3a192baf9f7b9296825c6535-f8a33.png 200w,
/static/2024-11-18-15-04-05-362804dc3a192baf9f7b9296825c6535-8ae0d.png 400w,
/static/2024-11-18-15-04-05-362804dc3a192baf9f7b9296825c6535-a3ca3.png 642w&quot; data-sizes=&quot;(max-width: 642px) 100vw, 642px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;解题要点-15&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于配置中心相关的面试题，首要的一个解题要点就是要明确配置中心应该具备哪些核心的功能，或者说配置中心在设计上应该有哪些需求。表面上，这是一道开放式的面试题，让面试者自己来给出配置中心的需求。但事实上，这道题的考查点还是相对固化的，需要面试者把平时在使用过程中关于配置中心的几点核心述求说明清楚。面试者可以基于自己的理解做一些发挥，但发挥的点应该也是围绕这些核心述求进行展开。&lt;/p&gt;
&lt;p&gt;对于一个配置中心而言，考虑到它的定位和作用，我们势必需要保证各种配置信息在各个环境中是相互隔离的，而这种隔离性则会导致数据的不一致，所以配置信息的一致性也是基本需求。对于面向生产环境的配置信息而言，我们还需要考虑安全性。这样，我们就梳理出了隔离性、一致性和安全性这几个核心诉求。当然，对于分布式环境下的配置信息管理，易管理性也是一项可以提到的诉求。&lt;/p&gt;
&lt;p&gt;对配置中心常见实现工具的介绍是面试过程中的另一个要点。面试官不会要求候选人对所有的配置中心工具都了如指掌，但我们需要熟练掌握至少一款开源框架。如果你比较擅长 Spring，那么本讲所介绍的 Spring Cloud Config 就是一个不错的选择。无论你选择哪一款工具来进入深入学习，都需要围绕整个配置信息流转的工作流程来系统把握配置中心的客户端和服务端组件之间的交互过程，明确配置信息的存储媒介和方式，以及前面提到的各种需求的实现方式。&lt;/p&gt;
&lt;p&gt;本质上，不同配置中心工具的设计思想是基本一致的。只要掌握了一款工具的底层实现原理，针对其他工具的使用方式以及实现机制的学习过程也会变得比较容易。这也可以说是技术类内容在学习上的一条客观规律。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-13&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;配置中心是分布式架构中的一个基础组件，而业界关于如何实现配置中心也有一些基本的模型和工具。在本讲内容中，我们针对配置中心实现需求梳理了配置中心所必须要考虑的组成结构和功能特性，并重点对 Spring 家族中的 Spring Cloud Config 展开了详细分析。&lt;/p&gt;
&lt;p&gt;在今天的内容中，我们也留下了一个伏笔：如果位于服务器端的配置信息发生了变更，如何确保这种变更能够实时的通知到客户端服务？这是一个比较复杂的话题，也是一个热门的面试题，让我们在下一讲中对它进行系统分析和讨论。&lt;/p&gt;
&lt;h1 id=&quot;配置中心：配置信息有变更时，如何实现热更新？&quot;&gt;&lt;a href=&quot;#%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%EF%BC%9A%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E6%9C%89%E5%8F%98%E6%9B%B4%E6%97%B6%EF%BC%8C%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E7%83%AD%E6%9B%B4%E6%96%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;配置中心：配置信息有变更时，如何实现热更新？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们详细介绍了配置中心的设计思想和组成结构，并基于 Spring 家族的 Spring Cloud Config 框架阐述了配置中心与各个服务之间的交互过程，理解了配置中心存储配置信息的方式，以及各个服务从配置中心中读取这些配置信息的工作原理。&lt;/p&gt;
&lt;p&gt;事实上，围绕配置中心，配置信息的传递有两个方向，一个是服务从配置中心进行拉取，另一个则是配置中心主动推送给各个服务，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-19-16-04-45-fd172740d73c6648d2008df70e798abb-3b971.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 379px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 73.35092348284961%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACsUlEQVQ4y3VT/VPaQBDN/98Z+2s7TlX8CIX6QykIIoEkBIzFpqK2iIwfM4CgfIMkkBDyuncSHGV6mZ3s3u2+u3d7T/A8D2xMpzYOYkEk4us4TGwgdbQFObMDKRVAOr3N/5IUQCq1heThBo6Sm4hFP1EcAYPw2EeO4APWag2c6CHy7sjKcOxT3N3E0GxI9D/AcJBDv6vCMnVaLy2sgqwaRrc74BhvAOv1JnLaHnlXZJcEoCEjfUbl+geO8wE0HpJoPqRwU4li5pxSzgXPy2SC6PWGr4Dz+ZwHtu3QYoyo7SEtBaHIYfJFRL6vc19VvmF3Z43sI1+X0yGivo18Tnqh7M3BsAS8G6ORhfF4AsuyYRhFNJstmOYUk4kDXS8gGo1TjknUbTw/W+/LITQaDXQ6HTw+PtIpp8uF+/s7otJbKej3e7i9vXkzV61WaeMm9aEGQZIkaJqGeDzOgf2Eer3OfUaD3Y1vbLRaLQK9XQLquo5cLkcdT0EYDAa0ax9PT09wXZf7pVKJJ7LYB/Evnc29MLjnJ2KDMWm322BYgmEYOD8/p7eWpjt5ZlUol8t0T6MliA/qN9A0TZ7jOA6PVVWFoigoFAoQGE1/B9u2l4WsgBW+Dl8AE87AdWfLDcfjMTd+wveXPp3OwA7COhiJxHB1VeHPwrZdDIdjFIuXvOuMOZtb6fILHQ+zmQtZTpD0tklWQXpje9jc+ICdwBpJMExvcZ/e5BfED0iKUohyRJLqFjVEXZx0vqqUbJYp5S9xNjB3f6HbUTCxTjDoq6hVExTLaLcy8GgdOCMr0t0HqZGjVenVag/4eRJeaJnk553BOBVRryYJUCNlrOPPxT6Mgkib/eZ651rO/kfLljVBIhEiKYnI57+SrkV6n8yCPFbkXZLfLrSsyNeOaU5VGP0IFs3ngP8AJ7ZGYDmUCmEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 19 16 04 45&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-19-16-04-45-fd172740d73c6648d2008df70e798abb-3b971.png&quot; data-srcset=&quot;/static/2024-11-19-16-04-45-fd172740d73c6648d2008df70e798abb-2c2c2.png 200w,
/static/2024-11-19-16-04-45-fd172740d73c6648d2008df70e798abb-3b971.png 379w&quot; data-sizes=&quot;(max-width: 379px) 100vw, 379px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中，数据推送主要发生在配置信息发生变更的场景，这也是面试过程中的一个高频问题，即位于配置中心的配置信息一旦有变更，应该如何确保各个服务实现热更新。本讲内容将围绕这一问题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-13&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-13&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在日常开发过程中，配置信息的热更新是一个实战性很强的问题。对于任何一个分布式服务系统而言，避免不了需要对配置信息做一些调整，例如根据业务需要添加一些新的配置项等。如果配置信息一有更新，就需要重启服务器，这显然是不可取的。所以，如何确保在服务不重启的前提下实现配置信息的热更新是开发人员都需要掌握的基本技能，自然也是面试官重点会考查的一个话题。&lt;/p&gt;
&lt;p&gt;针对这一话题，常见的面试问题包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Cloud Config 是如何实现配置信息热更新的？&lt;/li&gt;
&lt;li&gt;如果让你设计一款通用的配置信息动态更新机制，你有什么思路？&lt;/li&gt;
&lt;li&gt;什么是 WebHook 机制？你能描述它的常见应用场景吗？&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;code class=&quot;language-text&quot;&gt;数据热更新&lt;/code&gt; 这一技术主题所延伸出来的话题。然而，我在面试过程中发现，开发人员普遍对配置信息的热更新机制缺乏必要的了解，甚至不够重视。导致这一现象的部分原因是配置中心的构建和维护工作往往是由运维人员在负责，普通开发人员没有机会去深入理解配置中心的工作原理。另一方面，因为目前市面上主流的配置中心实现工具（例如 Spring Cloud Config、Nacos 等）都内置了完善的热更新机制，该机制对开发人员是透明的，也会导致我们对这部分功能缺乏对应的认知。&lt;/p&gt;
&lt;p&gt;如果你是一名中级开发人员，想要往高级开发甚至架构师方向发展，那么我认为不能只停留在使用工具的层次，而是应该深入框架的原理，做到触类旁通。而这一问题考查的正是开发人员对框架底层原理的掌握程度。回答好这一问题有助于拉开你和其他候选人之间的差距。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-14&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;通过上一讲内容的介绍，我们已经知道各个服务会在启动时从配置中心获取配置，然后缓存到本地。而一旦服务启动成功，无论配置中心中的配置信息如何变更，作为配置中心客户端的各个服务都不会再次主动获取配置信息，而是会利用位于本地的缓存配置。那么问题就来了，这些服务如何能够实时获取更改后的配置呢？&lt;/p&gt;
&lt;p&gt;在前面的讨论中，我们提到了一个词叫触类旁通。实际上，作为分布式服务系统的重要组成部分，配置中心的结构和原理与注册中心比较类似。像分布式协调框架 ZooKeeper 即可以构建注册中心，也可以用来实现配置中心。因此，我们在分析配置中心的热更新机制时，可以把它和注册中心做一些类比。&lt;/p&gt;
&lt;p&gt;在介绍注册中心的服务实例更新机制时，我们提到了有两种方案可以做到注册中心与各个服务之间的信息同步，分别是基于拉模式的定时更新策略，以及基于推模式的监听器机制。类似地，我们可以把这两种思路也应用到配置中心中，来具体分析它们的可行性。&lt;/p&gt;
&lt;p&gt;现在，针对这一问题我们已经具备基本的思路了。当然，不同的工具有不同的实现方式。针对配置中心，我们将继续基于 Spring Cloud Config 来讨论热更新机制。Spring Cloud Config 作为 Spring 家族的一员，很多功能特性与 Spring 框架息息相关，值得我们对它的技术体系做深入理解。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-16&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&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;使用 Spring Boot Actuato 组件。 这是 Spring Boot 提供的一个专门用来监测和管理系统运行时状态的组件。Actuator 是一种集成化组件，可以获取应用系统的运行时数据和配置信息并进行统计分析。&lt;/p&gt;
&lt;p&gt;当在代码类路径中引入 spring-boot-starter-actuator 依赖并启动 Spring Boot 应用程序时，我们会在启动日志里发现自动添加了 info、health、refresh 等众多 HTTP 端点。通过调用 &lt;code class=&quot;language-text&quot;&gt;/actuator/refresh&lt;/code&gt; 端点就可以刷新服务的配置信息，从而实现不重启 Spring Boot 应用的配置热更新。&lt;/p&gt;
&lt;p&gt;这种方案采用的就是典型的拉模式。这是一种简单有效的处理方式，但并不是最好的处理方式。因为针对每个微服务一般都会存在多个运行时实例，这样就需要把客户端的 &lt;code class=&quot;language-text&quot;&gt;/actuator/refresh&lt;/code&gt; 端点逐个进行调用，这点显然也不是很合理。而且，这种方案属于客户端自身行为，跟配置服务器端没有关系。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集成 Spring Cloud Bus 组件。 当我们在 Spring Cloud Config 服务器端代码工程的类路径中添加 Spring Cloud Bus 的引用并启动应用程序之后，Spring Boot Actuator 就为我们提供了 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点，通过访问该端点就可以达到对客户端所有服务实例的配置信息进行自动更新的效果。在这种方案中，服务端会主动通知所有客户端进行配置信息的更新，这样我们就无需关注各个客户端，而只对服务端进行操作即可。显然，这里用到的是基于推模式的动态监听机制。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过对上述实现方案的分析，我们可以明确第三种方案是我们需要回答的重点。在这种方案中，整个实现过程我们至少要搞清楚三大问题，即：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何自动调用服务器端所暴露的 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点？&lt;/li&gt;
&lt;li&gt;客户端如何得知服务器端的配置信息已经更新？&lt;/li&gt;
&lt;li&gt;客户端如何实时获取服务器端所更新的配置信息？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;源码解析-14&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;针对以上这三个问题，接下来我们将结合 Spring Cloud Config 源码逐一展开讨论。&lt;/p&gt;
&lt;h3 id=&quot;如何自动调用服务器端所暴露的-actuatorbus-refresh-端点？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%87%AA%E5%8A%A8%E8%B0%83%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%89%80%E6%9A%B4%E9%9C%B2%E7%9A%84-actuatorbus-refresh-%E7%AB%AF%E7%82%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何自动调用服务器端所暴露的 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点？&lt;/h3&gt;
&lt;p&gt;在现代软件开发过程中，开放式平台是一种常见的软件服务形态。我们可以把 Spring Cloud Config Server 所提供的 HTTP 端点视为一种开放式的接口，以供 Git 等第三方工具进行访问和集成。这样，我们把服务器端 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点对外进行暴露，然后第三方工具通过这个暴露的端点进行集成。&lt;/p&gt;
&lt;p&gt;例如，在 GitHub 中就设计了一种 Webhook 机制，并提供了用户界面供我们配置所需要集成的端点以及对应的操作，操作方法如下图所示（该图来自 GitHub 官方网站）：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-19-16-05-43-cc4c6bcbc4fdf8cb40d42a5d9e23d476-28558.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 514px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 91.24513618677042%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAACAElEQVQ4y5VUTW/UMBDdn84P4MAf4ISE6IErBw5w51AhtRInLrBqtxsncZzYjuMkj3nOx+6W0oZITxM7M+OZN8/ZVXWNB1UgLzX29wf82t/JewUfIlzbwc9wZ3i8PiFg11iLvChRVRW01sjzPNlGDjLGQMt+KWvDtaCa92jruhGfOoHr0HXY1eJ0ODzgoErcZQXuaY859sciVW7MFJxQmZRcVzodShRlmfZY2DiO2Hnv08K4gNIGVD7CCPKmg5W2gTE5bsEwDNJy0+CYqXTa0Me02fe9OAzJqed6WOwg36fgy2daTxW2LQotPLEd4UILL5q8NBaxH9BJ8i6eENNhpwQL1oTWOakwg1IKIQTEGBM6IZhgxamCJfAs+DzpmrCXNpXKoebpliRZkMsBnDyTP36e4m9N2EhrxMLEuDIy2WHlbsZf/F0etOukgsAWF576MztzGIfJtkIBfZ+dMnkjj07k48RakZCdLTXKdwqa3zfJhnxlwtciUAqYwUxESTnnUYulXl96Zg7l+tQmBXMITPRcwL+wJmSbvItsj1USTErJjE9o7cUK2Yr1wmErHIr1M5e86FuruxyK9QhG2pzllkQdJ1FzYEQroPOmhLfZT7y7+YQPt5/x8ccXKFOmKjnlpVrqdLoxG1r++vsar769xevr93jz/Qp15/5rIMvPZMEfkW59F2URsekAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 19 16 05 43&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-19-16-05-43-cc4c6bcbc4fdf8cb40d42a5d9e23d476-28558.png&quot; data-srcset=&quot;/static/2024-11-19-16-05-43-cc4c6bcbc4fdf8cb40d42a5d9e23d476-843f4.png 200w,
/static/2024-11-19-16-05-43-cc4c6bcbc4fdf8cb40d42a5d9e23d476-8216e.png 400w,
/static/2024-11-19-16-05-43-cc4c6bcbc4fdf8cb40d42a5d9e23d476-28558.png 514w&quot; data-sizes=&quot;(max-width: 514px) 100vw, 514px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们可以在上图的 Payload URL 中设置 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点地址。所谓的 Webhook，实际上就是一种回调。通过 Webhook，当我们提交代码时，GitHub 就会自动调用所配置的 HTTP 端点。也就是说，可以根据配置项信息的更新情况自动实现对 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点的访问。基于 GitHub 的配置仓库实现方案，我们可以得到如下图所示的系统结构：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-19-16-06-08-5c1a0e74728195a13c8a09cc85fd01f3-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 35.394126738794434%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABSElEQVQoz4VQa0+DQBDk//8YP5EKbdqa2qZaa6PhEYpAnzysQAEhtBAYl4uQGE2cZHJ3u3uzs8vxsgxht0PPMMBvNni2LFRFge12i9VqhfV6DZlqNE2DrutQFAWSJCGOYzSo65qxBcfTRzEMcWOaEJMED/SOfB8BxUJiEATs9CnW0HVdXC6XTuiXoEkFGlE9HvFC7l5VtUtWVYWC3JZl2X3K8xzh+dzlq/onufJ6RUIFnm0jJYdZmsInV57nQSXxZjxDf0MSJ12jKIxoVvwJznEcthuNdqWQQDtOO27gBzAPFgzbhOVusDvtsZCesJSX2H8cYDkWi5uUNw4GOPyDKIowdu5xl87QswcQTyOI70MIxJ4zgOAN6T3CJJtjdl2Aa9ywXXyzXXJzb5ClGcY2CX7OwO9FCO6QsX8aU4M+a3JLwpN0jmn+iC8t5wlT0B+XHAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 19 16 06 08&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-19-16-06-08-5c1a0e74728195a13c8a09cc85fd01f3-a40ca.png&quot; data-srcset=&quot;/static/2024-11-19-16-06-08-5c1a0e74728195a13c8a09cc85fd01f3-3426a.png 200w,
/static/2024-11-19-16-06-08-5c1a0e74728195a13c8a09cc85fd01f3-f3a4f.png 400w,
/static/2024-11-19-16-06-08-5c1a0e74728195a13c8a09cc85fd01f3-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&quot;客户端如何得知服务器端的配置信息已经更新？&quot;&gt;&lt;a href=&quot;#%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A6%82%E4%BD%95%E5%BE%97%E7%9F%A5%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E7%9A%84%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%B7%B2%E7%BB%8F%E6%9B%B4%E6%96%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;客户端如何得知服务器端的配置信息已经更新？&lt;/h3&gt;
&lt;p&gt;接下来我们关注第二个问题，即客户端如何得知服务器端的配置信息已经更新？&lt;/p&gt;
&lt;p&gt;我们首先需要明确，在调用了 &lt;code class=&quot;language-text&quot;&gt;/actuator/bus-refresh&lt;/code&gt; 端点之后，系统内部发生了什么事情。这里我们需要快速浏览 Spring Cloud Bus 中的代码工程，发现在 &lt;code class=&quot;language-text&quot;&gt;org.springframework.cloud.bus.endpoint&lt;/code&gt; 包下存在一个 RefreshBusEndpoint 端点类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;33853938237413230000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Endpoint(id = &amp;quot;bus-refresh&amp;quot;)
public class RefreshBusEndpoint extends AbstractBusEndpoint {
   public RefreshBusEndpoint(ApplicationEventPublisher context, String id) {
      super(context, id);
   }

   @WriteOperation
   public void busRefreshWithDestination(@Selector String destination) {
      publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), destination));
   }

   @WriteOperation
   public void busRefresh() {
      publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
   }
}`, `33853938237413230000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Endpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bus-refresh&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshBusEndpoint&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AbstractBusEndpoint&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshBusEndpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApplicationEventPublisher&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@WriteOperation&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;busRefreshWithDestination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Selector&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshRemoteApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstanceId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@WriteOperation&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;busRefresh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshRemoteApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstanceId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，RefreshBusEndpoint 类对应于我们前面访问的 &lt;code class=&quot;language-text&quot;&gt;/bus-refresh&lt;/code&gt; 端点。可以看到，Spring Cloud Bus 在这里做的事情仅仅只是发布了一个新的 RefreshRemoteApplicationEvent 事件。&lt;/p&gt;
&lt;p&gt;既然发送了事件，我们就需要寻找该事件的监听者。我们在 Spring Cloud Bus 的 &lt;code class=&quot;language-text&quot;&gt;org.springframework.cloud.bus.event&lt;/code&gt; 包下找到了 RefreshRemoteApplicationEvent 事件的监听器 RefreshListener，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;24994877814835160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class RefreshListener implements ApplicationListener&lt;RefreshRemoteApplicationEvent&gt; {
   private static Log log = LogFactory.getLog(RefreshListener.class);

   private ContextRefresher contextRefresher;

   public RefreshListener(ContextRefresher contextRefresher) {
      this.contextRefresher = contextRefresher;
   }

   @Override
   public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
      Set&lt;String&gt; keys = contextRefresher.refresh();
      log.info(&amp;quot;Received remote refresh request. Keys refreshed &amp;quot; + keys);
   }
}`, `24994877814835160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshListener&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationListener&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RefreshRemoteApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Log&lt;/span&gt; log &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RefreshListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContextRefresher&lt;/span&gt; contextRefresher&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RefreshListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ContextRefresher&lt;/span&gt; contextRefresher&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contextRefresher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contextRefresher&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RefreshRemoteApplicationEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contextRefresher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Received remote refresh request. Keys refreshed &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; keys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从类的定义中，我们可以看到该监听器就是用来处理 RefreshRemoteApplicationEvent 事件，其在 onApplicationEvent 方法中同样也是调用了 ContextRefresher 中的 refresh 方法进行配置属性的刷新。&lt;/p&gt;
&lt;p&gt;请注意，RefreshRemoteApplicationEvent 是一个远程事件，将通过消息中间件进行发送，并被 Spring Cloud Config 客户端所监听，处理流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-19-16-06-40-e772897121850a89c3478b4b652bbb1f-58f3c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 627px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 61.56299840510366%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAAB10lEQVQoz41T/XOaQBDl//8/Ov3JdJo0fmHoNEk7rYmpWtIJoHyIcGgiJiMcAsLrHlY77eg0O/PY2zt4897uIXGeQP3RwMPPd+h+ewt1eIKRcQrD6EFEWRaE8tWQ0jSF4/RgmdfoyG+gqh1MnM+YTvXfhCUOxbF9STwWi+cKrutjvc6RJNn+haIosVol4DytINZpmu9Jy13eKRQHz+ECy6cn5Ov1nmi5XBJ5BsZGcCeX0DUFuq7AsS+pJdfI881BpZLl+7Dnc2iui+F4jDHVMefwPI9s++h2FfhTBbc3NfRuazD0JjTtAlmaHSY8ub9HO1yi9fgIOQzx3plAs21s8hxxHJP9BFEkLK+REKKIV8qP9VGqqSrOSNU5oR4EqJFK3XHwv/hD9q9lsjZiDNZsBousG94UpmUhjiKIG7D7sCiKv1EeRjWUIstoMCFWLy/gRBQRfFIcLkLwmJP9DV4bFSEXQyACgRmpDMj6nBSzeYAHR8PAGOK7NsCYmTADa5uZVa1FFrXhjWAzB9KxCyrizuxDia7QniuoM5n63K5yI+hsa29bt+j8E/+ChiVvFR76hUAu+/YAX8seWv4FTs06GlMZZ3YTLabg3G1Xex+cZrV/gz4+siv8AvCriUjvG8GrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 19 16 06 40&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-19-16-06-40-e772897121850a89c3478b4b652bbb1f-58f3c.png&quot; data-srcset=&quot;/static/2024-11-19-16-06-40-e772897121850a89c3478b4b652bbb1f-a8bea.png 200w,
/static/2024-11-19-16-06-40-e772897121850a89c3478b4b652bbb1f-f4947.png 400w,
/static/2024-11-19-16-06-40-e772897121850a89c3478b4b652bbb1f-58f3c.png 627w&quot; data-sizes=&quot;(max-width: 627px) 100vw, 627px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&quot;客户端如何实时获取服务器端所更新的配置信息？&quot;&gt;&lt;a href=&quot;#%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E6%97%B6%E8%8E%B7%E5%8F%96%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%89%80%E6%9B%B4%E6%96%B0%E7%9A%84%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;客户端如何实时获取服务器端所更新的配置信息？&lt;/h3&gt;
&lt;p&gt;最后需要明确的第三个问题是，客户端如何获取服务器端所更新的配置信息？这就需要梳理 Spring Cloud Config Server 与注册中心之间的关系。&lt;/p&gt;
&lt;p&gt;我们在上一讲分析配置中心的基本模型时提到，配置中心作为整个微服务架构运行所需的基础服务，需要确保其可用性。因为配置服务本身也是一个独立的微服务，所以 Spring Cloud Config 实现高可用的方式很简单。跟其他微服务一样，也可以把自己注册到注册中心上，让其他服务提供者或消费者通过注册中心进行服务发现和获取。&lt;/p&gt;
&lt;p&gt;显然，在这种方式下，基于注册中心的服务治理机制同时提供了服务器端的负载均衡和客户端的配置功能，从而也就间接实现了高可用性。从另一个角度，我们也可以理解为可以通过注册中心获取所有 Spring Cloud Config 服务的实例，从而在分布式环境下为获取配置信息提供了一种简便的手段。&lt;/p&gt;
&lt;p&gt;Spring Cloud Config 提供了一个工具类 ConfigServerInstanceProvider 来完成与注册中心之间的交互，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;59381959467975000000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ConfigServerInstanceProvider {
   private static Log logger = LogFactory.getLog(ConfigServerInstanceProvider.class);
   private final DiscoveryClient client;

   public ConfigServerInstanceProvider(DiscoveryClient client) {
      this.client = client;
   }

   @Retryable(interceptor = &amp;quot;configServerRetryInterceptor&amp;quot;)
   public List&lt;ServiceInstance&gt; getConfigServerInstances(String serviceId) {
      logger.debug(&amp;quot;Locating configserver (&amp;quot; + serviceId + &amp;quot;) via discovery&amp;quot;);

      // 根据 serviceId 获取服务实例列表
      List&lt;ServiceInstance&gt; instances = this.client.getInstances(serviceId);
      if (instances.isEmpty()) {
         // 如果服务实例列表为空则抛出异常
      }

      return instances;
   }
}`, `59381959467975000000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigServerInstanceProvider&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Log&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigServerInstanceProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DiscoveryClient&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfigServerInstanceProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DiscoveryClient&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Retryable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;configServerRetryInterceptor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConfigServerInstances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Locating configserver (&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; serviceId &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;) via discovery&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 根据 serviceId 获取服务实例列表&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; instances &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;instances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果服务实例列表为空则抛出异常&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; instances&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在这里，我们看到了熟悉的 DiscoveryClient，DiscoveryClient 通过同样熟悉的 getInstances 方法从注册中心中获取 Spring Cloud Config 服务实例，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28615593057279120000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`List&lt;ServiceInstance&gt; instances = this.client.getInstances(serviceId);`, `28615593057279120000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; instances &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;ConfigServerInstanceProvider 的调用者是 DiscoveryClientConfigServiceBootstrapConfiguration。如果系统中生成了 ContextRefreshedEvent 事件就会触发如下所示的 refresh 方法（部分代码做了裁剪）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1801421687270732300&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void refresh() {
   try {
      String serviceId = this.config.getDiscovery().getServiceId();
      List&lt;String&gt; listOfUrls = new ArrayList&lt;&gt;();
      // 根据 serviceId 获取配置服务实例列表
      List&lt;ServiceInstance&gt; serviceInstances = this.instanceProvider.getConfigServerInstances(serviceId);

      for (int i = 0; i &lt; serviceInstances.size(); i++) {
         ServiceInstance server = serviceInstances.get(i);
         String url = getHomePage(server);
         // 处理安全机制
         if (server.getMetadata().containsKey(&amp;quot;password&amp;quot;)) {
            String user = server.getMetadata().get(&amp;quot;user&amp;quot;);
            user = user == null ? &amp;quot;user&amp;quot; : user;
            this.config.setUsername(user);
            String password = server.getMetadata().get(&amp;quot;password&amp;quot;);
            this.config.setPassword(password);
         }

         // 构建配置文件的访问地址列表
         if (server.getMetadata().containsKey(&amp;quot;configPath&amp;quot;)) {
            String path = server.getMetadata().get(&amp;quot;configPath&amp;quot;);
            if (url.endsWith(&amp;quot;/&amp;quot;) &amp;&amp; path.startsWith(&amp;quot;/&amp;quot;)) {
               url = url.substring(0, url.length() - 1);
            }
            url = url + path;
         }

         listOfUrls.add(url);
      }

      // 设置配置文件地址列表
      String[] uri = new String[listOfUrls.size()];
      uri = listOfUrls.toArray(uri);
      this.config.setUri(uri);
   }
}`, `1801421687270732300`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; serviceId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDiscovery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getServiceId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; listOfUrls &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 根据 serviceId 获取配置服务实例列表&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; serviceInstances &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instanceProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConfigServerInstances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;serviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; serviceInstances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;ServiceInstance&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serviceInstances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHomePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 处理安全机制&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// 构建配置文件的访问地址列表&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;configPath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;configPath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         listOfUrls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 设置配置文件地址列表&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;listOfUrls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; listOfUrls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上述 refresh 方法中可以看到，Spring Cloud Config 首先会获取配置文件中的 spring.cloud.config.discovery.serviceId 配置项所指定的服务实例 id，然后根据 serviceId 从 ConfigServerInstanceProvider 中获取注册服务的实例对象集合 serviceInstances，最后循环遍历 serviceInstances 来更新存储在内存中的配置属性值。通过上述流程，我们确保位于客户端中的配置信息得到了实时的更新。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-16&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于配置中心的热更新机制是一道比较典型的技术原理类面试题。同样，这道题之所以典型也是因为很多开发人员只停留在对具体工具的使用上，而并不清楚其背后的设计思想和原理。我在面试过程中，能够碰到让我满意的回答并不多。&lt;/p&gt;
&lt;p&gt;从解题思路上讲，如果你对配置中心本身并没有太多概念，那么这道题有一定的难度。没有一些基础知识，光靠凭空想象是很难梳理热更新背后的实现原理的。所以在日常开发过程中，对于那些构成分布式服务系统的基础技术组件，就算不需要自己动手进行开发或维护，还是需要专门做一些了解。这也是你和普通开发人员的一个重要区别。&lt;/p&gt;
&lt;p&gt;另一方面，配置中心的热更新是一个相对复杂的过程，考查的知识点也比较多。例如，以本讲介绍的 Spring Cloud Config 为例，就涉及到 Spring 内置的事件通知和上下文刷新机制、Spring Boot Actuator、Spring Cloud Bus 等工具框架。同时，Spring Cloud Config 还会基于注册中心获取各个配置服务实例并动态更新其位于内存中的配置信息。我们在回答过程中，对这些组件都应该有所提及，从而向面试官展示自己的知识广度。&lt;/p&gt;
&lt;p&gt;在本讲的问题分析部分，我们实际上也提到了可以类比注册中心来学习配置中心。对于配置中心而言，具体的实现工具和框架是比较丰富的，常见的就包括 Etcd、Consul、Disconf、Diamond、Nacos 以及 Spring 家族的 Spring Cloud Config。而这些工具在实现过程中可能各有差别，但在设计思想上基本都是一致的。技术体系之间的相通性也是我们回答技术原理类面试题的一个有效抓手。如果你掌握了某一种配置中心实现工具的基本原理，那么即使没有使用过其他工具，面对类似本讲中提出的问题也能够做到触类旁通。至于如何基于技术体系相通性来应对技术原理类面试，本课程后面还会有专题进行讨论。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-14&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;作为一个典型的技术原理类问题，本讲内容基于 Spring Cloud Config 框架剖析了实现配置信息热更新的工作原理。我们抛出了三个与这个主题相关的核心问题，然后基于源码对这些问题都做了一一解答。&lt;/p&gt;
&lt;p&gt;事实上，Spring Cloud Config 作为 Spring 自研的配置中心框架，其内部大量使用了 Spring 框架现有的功能特性，这点与我们学习其他配置中心的实现工具不同。我们需要首先对 Spring 容器相关的知识体系有足够的了解，才能更好地理解 Spring Cloud Config 的设计和实现方式。&lt;/p&gt;
&lt;p&gt;讨论完配置中心之后，下一讲我们将面对一个新的问题，即链路跟踪。在分布式服务系统构建过程中，链路跟踪同样是一个基础技术组件。那么，如何对服务链路进行有效监控呢？我们下一讲再聊。&lt;/p&gt;
&lt;h1 id=&quot;链路跟踪：如何对服务链路进行有效监控？&quot;&gt;&lt;a href=&quot;#%E9%93%BE%E8%B7%AF%E8%B7%9F%E8%B8%AA%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AF%B9%E6%9C%8D%E5%8A%A1%E9%93%BE%E8%B7%AF%E8%BF%9B%E8%A1%8C%E6%9C%89%E6%95%88%E7%9B%91%E6%8E%A7%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;链路跟踪：如何对服务链路进行有效监控？​&lt;/h1&gt;
&lt;p&gt;通过前面几讲内容的介绍，我们已经掌握了注册中心、服务网关、配置中心等一系列技术组件。基于这些组件，我们可以构建完整而强大的分布式系统。&lt;/p&gt;
&lt;p&gt;在分布式系统中，一组独立的服务相互协作完成特定的业务功能。而服务之间的调用不可避免会出现各种问题，这时候就需要引入分布式链路跟踪机制来定位和解决这些问题。&lt;/p&gt;
&lt;p&gt;那么，如何对服务链路进行有效监控呢？业界关于分布式链路跟踪也有统一的规范以及代表性的实现框架。本讲内容将围绕这些规范和框架展开详细的讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-14&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-14&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在分布式系统中，我们基于业务划分服务，并对外暴露服务访问接口。试想这样一个场景，如果我们发现某一个业务接口在访问过程中发生了错误，一般的处理过程就是快速定位到问题所发生的服务并进行解决。但在现实的中大型系统中，一个业务接口背后可能会调用一批其他业务体系中的业务接口或基础设施类的底层接口，这时候我们如何能够做到快速定位问题呢？&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-11-50-41-64ef2320b287fe6003c55bfb7b16d314-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 66.40866873065015%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACS0lEQVQ4y42T61LaUBSF85LtC/TB2plapkprVQqCogI1EQEDEkEGCyTcw51UoDgkXw+BOlJbp3tmzflx1qy99k3iH+HYC/etakck375G9bwR7yuK8jucFQHHcZ5Beib05HMZP3o12rkg5dQ+jayfUavwm/jIeRrS37IsYdu2SxhbfcxeiZv8Ja1OkcGwzUrH+T+Hf0a5lEVNeYkrO1woW5S+J92SbWchYD+DNL63MEc9OsJJxxpgDnvCSQu9pruYTKbC7aq02ewn47H1ogHJp/mJ3fmI5PeI3e5zkPAQqB0TtWROzBjdQdclLrMvY2gNkQsKajWD1r0la+bRBLLtHNc1DelA20e93UVJfuD8couzzA4JslyRQ5knaZgNV2ixnnq5VSEykznQv/I55yXUDBKo+jkdR4lMZKRwPULcUbmwVU7rxwTUbeRqCMUIctoI0+qbK4frIelt3eWqeohCdpdCwUcxt0eiF3ONSCfNKGk0kgJLkWTqI3LSw6Uo/ey7n+bA3HBYEYLR+QWBRhB/xYc3t4NX28ZXOyQyVZBClRO+zeKcjc7ZFb30FvY4bIYJCIQ7Udrd9kYPzZ6JUoy7fbtu33AleplpaqR0lUxNODRFSYZZpWrWMNpVAYNat06lWeHOKG1M0LIsptMpi7nNfPJAXa9zP5rgPIgtsN3VfHkP+8Jdr9cSQn1arTrz+Xw1mHIKTftCPL5NPu+j0SyyXvXVpWws57pXpnCcTn9CPn9PIu4RAkePp9bpGGLhU0I4jWGoImnDvZ7l4H4BSwDBaqk/uYMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 11 50 41&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-11-50-41-64ef2320b287fe6003c55bfb7b16d314-0e173.png&quot; data-srcset=&quot;/static/2024-11-20-11-50-41-64ef2320b287fe6003c55bfb7b16d314-2fb9f.png 200w,
/static/2024-11-20-11-50-41-64ef2320b287fe6003c55bfb7b16d314-f1e72.png 400w,
/static/2024-11-20-11-50-41-64ef2320b287fe6003c55bfb7b16d314-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;传统的做法是通过查阅服务器的日志来定位问题，但在中大型系统中，这种做法可操作性并不强，主要原因在于我们很难找到包含错误日志的那台服务器。一方面，开发人员可能都不知道整个服务调用链路中具体有几个服务，也就无法找到是哪个服务发生了错误。就算找到了目标服务，在分布式集群的环境下，我们也不建议直接通过访问某台服务器来定位问题。服务监控的需求就应运而生。&lt;/p&gt;
&lt;p&gt;从上述描述中，我们不难看出服务监控是分布式系统的基础需求之一。可以说，链路跟踪是构建分布式系统过程中必不可少的一个技术组件。因此，在面试过程中，面试官也会经常对这一主题进行考查，常见的提问方式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在服务监控领域，Trace 和 Span 分别代表什么含义？&lt;/li&gt;
&lt;li&gt;你能描述分布式服务链路跟踪的基本原理吗？&lt;/li&gt;
&lt;li&gt;针对一个完整的远程调用请求和响应过程，应该如何计算所消耗的时间？&lt;/li&gt;
&lt;li&gt;如果想要获取可视化的链路跟踪数据，可以采用什么工具和框架？&lt;/li&gt;
&lt;li&gt;如果你想在服务调用链路中添加自定义的性能指标，有什么实现方案？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;服务监控相关的面试题提问方式实际上还是比较固定的，让我们对这类问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-15&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;对于服务监控类的面试题而言，我们的切入点主要在于两个方面，一方面是分布式服务跟踪的基本原理，另一方面则是目前主流的实现工具和框架。&lt;/p&gt;
&lt;p&gt;关于分布式服务跟踪和监控的运行原理，实际上并不是特别复杂，我们首先需要引入两个基本概念，即 Trace 和 Span。&lt;/p&gt;
&lt;p&gt;其中，Trace 代表一次请求和响应过程中的整个调用链路，而 Span 则代表这个链路中的一段跨度。显然，Trace 和 Span 是一对多的关系。通过将一个个具体的 Span 聚合起来，我们就可以构建出一条完整的链路并进行有效的跟踪。围绕这两个概念，我们可以进一步引出链路跟踪过程中的 4 种关键事件并开展性能分析。这是回答这类面试题的第一点思路。&lt;/p&gt;
&lt;p&gt;回答这类面试题的第二点思路是将这些原理应用到具体的框架中。这里可以选择一些主流的开源框架展开讨论，例如本讲中要介绍的 Spring Cloud Sleuth。对于这些工具，一方面我们需要对调用链路中所生成的监控数据进行收集和分析，另一方面也需要介绍一些可视化的工具来展示这些数据分析的结果。在面试过程中，面试官一般都会考查候选人对工具框架的具体掌握程度，需要我们对它们的功能特性有全面的了解。&lt;/p&gt;
&lt;p&gt;最后，我们再次回到实践。一旦掌握分布式服务跟踪原理和相关的工具，我们就可以按照自身的需要来定制化链路构建过程。这在我经历的面试过程中也是经常会被考查的一个问题点。面试官喜欢基于这种面试主题中对候选人的工程实践能力进行充分的考查，这就需要开发人员能够结合具体的场景给出工具应用的方式，最好能够总结一定的最佳实践。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-17&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;h3 id=&quot;分布式服务跟踪基本原理&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E8%B7%9F%E8%B8%AA%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式服务跟踪基本原理&lt;/h3&gt;
&lt;p&gt;我们先来分析一下分布式服务跟踪的基本原理，这里需要引入两个 Id，即 TraceId 和 SpanId，它们分别代表一个全局唯一的 Trace 和 Span。&lt;/p&gt;
&lt;p&gt;TraceId 即跟踪 Id。在分布式架构中，每个请求会生成一个全局的唯一性 Id，通过这个 Id 可以串联起整个调用链。也就是说，当请求在分布式系统内部流转时，系统需要始终保持传递该唯一性 Id，直到请求返回。这个唯一性 Id 就是 TraceId。&lt;/p&gt;
&lt;p&gt;除了 TraceId 外，我们还需要 SpanId，SpanId 一般被称为跨度 Id。所谓跨度，就是调用链路中的其中一段时间，具有明确的开始和结束这两个节点，这样通过计算开始时间和结束时间之间的时间差，我们就能明确调用过程在这个 Span 上所产生的时间延迟。&lt;/p&gt;
&lt;p&gt;通过前面的介绍，我们不难理解 TraceId 和 SpanId 之间是一对多的关系，即在一个调用链路中只会存在一个 TraceId，但会存在多个 SpanId。这样多个 SpanId 之间就会有父子关系，即链路中的前一个 SpanId 是后一个 SpanId 的父 SpanId，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-11-54-41-573c4af0e6c5cc793143fa10d23940b0-b03fa.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 650px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 24.76923076923077%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABN0lEQVQY00WQ2U7CYBCF+/6vYLxQTFSQTQqtXVRcKpSlpQShrVYiRYKNkqBXgn7+VI0XJzOZ+U7mZKSjZpHaWKMSKr9SU8l3KrVI46hf5qCb59DJk3ULVO9PUv3zPx45EhqpSNl2AW1uos0MzOQM4/kUY36KEmvUYmEeK2Sae2ydb7PvHKA+6VQnasr98fqGn+koYx1pNpvT7R4zjVU8L8doWOQhqhJFMo3GLtOpxu1tgbt7mcdHFd8vcWPtMBG918syGORTNooU+n0Pab3+JPAt+l4F09jDusrhiANhYIoq03Mq6CcZri+zdFol/JFBu1XGFYypZ7DFyzZMGJyxeF0gwRcfHyve3t4JwpDlcikSDanXL0RyF8dxSZKEl5dXXLcn5pd0Og7D4QjbtrGsG+J4mu5XqxXfhb5R4dgd4GQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 11 54 41&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-11-54-41-573c4af0e6c5cc793143fa10d23940b0-b03fa.png&quot; data-srcset=&quot;/static/2024-11-20-11-54-41-573c4af0e6c5cc793143fa10d23940b0-6fb5f.png 200w,
/static/2024-11-20-11-54-41-573c4af0e6c5cc793143fa10d23940b0-d11a1.png 400w,
/static/2024-11-20-11-54-41-573c4af0e6c5cc793143fa10d23940b0-b03fa.png 650w&quot; data-sizes=&quot;(max-width: 650px) 100vw, 650px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;理解了 TraceId 和 SpanId 的概念之后，我们就需要对整个分布式调用链路进行进一步拆分，从而细化控制的粒度。业界一般通过四种不同的注解（Annotation）记录每个服务的客户端请求和服务器响应过程。这里的注解实际上代表的就是链路中所发生的关键事件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cs 注解: cs 代表 Client Send，即客户端发送请求，启动整个调用链路。&lt;/li&gt;
&lt;li&gt;sr 注解: sr 代表 Server Receive，即服务端接收请求。显然 &lt;code class=&quot;language-text&quot;&gt;(sr - cs)&lt;/code&gt; 值代表请求从客户端到服务器端所需要的网络传输时间。&lt;/li&gt;
&lt;li&gt;ss 注解: ss 代表 Server Send，即服务端把请求处理结果返回给客户端，&lt;code class=&quot;language-text&quot;&gt;(ss - sr)&lt;/code&gt; 值代表服务器端处理该请求所需要的时间。&lt;/li&gt;
&lt;li&gt;cr 注解: cr 代表 Client Receive，即客户端接收到服务端响应，结束调用链路。&lt;code class=&quot;language-text&quot;&gt;(cr - sr)&lt;/code&gt; 值代表响应结果从服务器端传输到客户端所需要的时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面结合一张示意图来进一步解释这四种注解之间的关联关系：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-11-55-27-62df4dfff108a29947160475f68488ea-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 43.65325077399381%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB6UlEQVQoz12Se3PSUBTE8/2/hNV/tNXRFgs6tFooBRLeL9tqoSmQEBISHgnkIYWfNxnttN6ZnTvZszl75tyVeHZmM5vlconruglWqxW2baOqKo7jJN//arHO8zz+P1JMuq6XFKemyXqzYRPD99nv9+x2OyzLSsTb7fZvLcAV+rt+P7ldb53wURgind6m6d9+wTYKyb20inSuM7z/cUJ71Hly/h2F5O9V0r9uuLtOYenfue0d49oFGp0Mh40WJ7UakvxYRVbTNPUspcFnOsY5spYlF5WpaLWk2V7AFxPkxaTl3Rb5vkLLaKE81IS+SVnvcb5yOVBkpAZdLoIKp7MLUpMzjvUzyvsWxW0FZVR7mjAQK8jpGjHzbRPxaWwKrUVqOkcW3KUwPCiVkOq0KVg5rgTyk/ME5VWJ4mOFqlZ/0fDSnFKNf7bG5A2V/PRBcEPK3pyc2OMbRUFKaxl6nSP08Vd+3nzEmmSpdj9wNEyJVSjPGm7IPgxJ6yO6rbdowzTd9iGOmSUvv+NVvcnrUhFpoA8whctUuLXbCovFBMMcMZjc4/le8spxRJJYidiohiFefcR8PmEwuMaxNXRjSF/XMUTEpOcZcpwF67VPGEREAmEQCoMF4/E4iUcYBIIPCETN90PRdCkMX+bwDyLLk56/pFSMAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 11 55 27&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-11-55-27-62df4dfff108a29947160475f68488ea-0e173.png&quot; data-srcset=&quot;/static/2024-11-20-11-55-27-62df4dfff108a29947160475f68488ea-2fb9f.png 200w,
/static/2024-11-20-11-55-27-62df4dfff108a29947160475f68488ea-f1e72.png 400w,
/static/2024-11-20-11-55-27-62df4dfff108a29947160475f68488ea-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;有了这四个注解之后，我们就可以使用它们来量化整个服务调用链路，从而找出潜在的问题。这里同样给出一个示意图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-14-05-46-2b55dd75f048c84c437d4a5a89850b82-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50.46439628482972%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAB/UlEQVQoz42S227aQBRF+f/P6ENbqQqp2pQkSkMChIKdEgh3c/MFO4AxGGwHczFeHYLaSK0q5UhbM5qHNfvscxJxHPO33lrraE++VyXfuEFq3ZLTmyTgAHjVb6gbuDiec5TvMPWOsr0pc99lv9sxC0IqqkS/dcLESKHMBfBgaL9/1aE2uw05T+ZHXKYQPVAQpzy5pd5L0dIuKJtZgiDA30TI6j199YLZPEfd7ZCwzDRDI42hX2NZN2w3Ia6/pDC6oenneZxlkLb3PE5vaZU/YakpenaeieMQrje0xyZVrUVdb9Ocjkjo6js67SST8RWW+YFVsMB9Dqh0vtFtfGYoXNVWEpIAtpUzrEma+viO+WL5J8t4FxGLPEVWAqh9pKucM7XvGI+SLFybZbii0k0xaH9lpF9SD2WK86x4O6MmHJaeskegAGwEaC1YLxL3hKae0O8m0QbCjXHKOhTZCGDRztJ4LlJd5pF3JapOhkHzC7b5HWWcY+Yu8FchhX6NR1WmpolPzc5hKHsRsIfvL8RQouM67Nakl3dkIpnMViIX/6Q4uqTRSoqWr8RQrrGnU7z1loeBRLX0HlU5pek0Dmvzb0UCPPRMdN9gGJi07Y5wpTCYdOmOFIyZ8dLuYcoPeole7wxHZNx0lYPDNyz2f3Z9G0XULI2yWhdt16k86fwCmNPkiFCpzOoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 14 05 46&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-14-05-46-2b55dd75f048c84c437d4a5a89850b82-0e173.png&quot; data-srcset=&quot;/static/2024-11-20-14-05-46-2b55dd75f048c84c437d4a5a89850b82-2fb9f.png 200w,
/static/2024-11-20-14-05-46-2b55dd75f048c84c437d4a5a89850b82-f1e72.png 400w,
/static/2024-11-20-14-05-46-2b55dd75f048c84c437d4a5a89850b82-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们看到这次请求的 TraceId 是 trace1，而 SpanId 根据不同的服务会发生变化。这里的四种注解构成了客户端和服务器对一次请求处理的闭环。对于服务 A 而言，cs 是 11:10:44，cr 是 11:10:55，也就说该次服务请求经由服务 A 的整个调用链路时间是 11s(11:10:44~11:10:55)，显然这个响应时间非常长。&lt;/p&gt;
&lt;p&gt;显然，通过这些注解我们就可以发现服务调用链路中存在的问题，目前主流的服务监控实现工具都对这些注解做了支持和封装。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-中的服务监控&quot;&gt;&lt;a href=&quot;#spring-cloud-%E4%B8%AD%E7%9A%84%E6%9C%8D%E5%8A%A1%E7%9B%91%E6%8E%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud 中的服务监控&lt;/h3&gt;
&lt;p&gt;通过前面所介绍的服务监控基本原理，我们明确了分布式环境下服务跟踪的载体，即 TraceId 和 SpanId。而在 Spring Cloud 中也存在一个组件能够帮忙我们自动生成 TraceId 和 SpanId，这个组件就是 Spring Cloud Sleuth。&lt;/p&gt;
&lt;p&gt;接下来，我们来看一下 Spring Cloud Sleuth 中链路跟踪的表现形式，如下所示的是一个具体的示例：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;90868804806048960000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`INFO [userservice,81d66b6e43e71faa,6df220755223fb6e,true] 18100 --- [nio-8082-exec-8] c.s.user.controller.UserController: Get user by UserName from userservice instance`, `90868804806048960000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;INFO [userservice,81d66b6e43e71faa,6df220755223fb6e,true] 18100 --- [nio-8082-exec-8] c.s.user.controller.UserController: Get user by UserName from userservice instance&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们关注于上述日志信息中的斜体部分内容，可以看到包括了四段内容，即服务名称、TraceId、SpanId 和 Zipkin 标志位，它是格式如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;73733584197809220000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`[服务名称, TraceId, SpanId, Zipkin 标志位]`, `73733584197809220000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[服务名称, TraceId, SpanId, Zipkin 标志位]&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，第一段中的 userservice 代表着该服务的名称，使用的就是在 Spring Boot 应用中 spring.application.name 配置项所指定的服务名称。考虑到服务跟踪的需求，为服务指定一个统一而友好的名称是一项最佳实践。&lt;/p&gt;
&lt;p&gt;第二段中的 TraceId 代表一次完整请求的唯一编号，上例中的 81d66b6e43e71faa 就是该次请求的唯一编号。在诸如 Zipkin 等可视化工具中，可以通过 TraceId 查看完整的服务调用链路。&lt;/p&gt;
&lt;p&gt;在一个完整的服务调用链路中，每一个服务之间的调用过程都可以通过 SpanId 进行唯一标识，例如上例中位于第三段的 6df220755223fb6e。所以 TraceId 和 SpanId 是一对多的关系，即一个 TraceId 一般都会包含多个 SpanId，每一个 SpanId 都从属于特定的 TraceId。当然，也可以通过 SpanId 查看某一个服务调用过程的详细信息。&lt;/p&gt;
&lt;p&gt;最后的第四段代表 Zipkin 标志位，该标志位用于识别是否将服务跟踪信息同步到 Zipkin。Zipkin 是一个可视化工具，可以将服务跟踪信息通过一定的图形化形式展示出来。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-15&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在本讲的源码解析部分，我们一方面分析 Spring Cloud Sleuth 生成和传递 Span 的实现原理。另一方面，我们也将基于更为底层的 Brave 框架给出创建自定义 Span 的实现方法。&lt;/p&gt;
&lt;h3 id=&quot;基于-spring-cloud-sleuth-生成和传递-span&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-spring-cloud-sleuth-%E7%94%9F%E6%88%90%E5%92%8C%E4%BC%A0%E9%80%92-span&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 Spring Cloud Sleuth 生成和传递 Span&lt;/h3&gt;
&lt;p&gt;在一次远程调用中，对于服务提供者而言，服务的消费者相当于是它的客户端。而对于调用过程中的其他服务，这个服务消费者也可能是它们的服务提供者。因此，取决于在链路中的具体位置，不同服务可能会扮演不同的角色，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-14-07-48-394925941b22c1fdef88c20d5973368e-9cb4a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 510px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.23529411764706%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACGElEQVQoz2WSCW/aQBCF/f9/R6tyk5SGpIUAEkrLYbAxmCNgbgeTcB/mNK/PS1ql6kijWXt33858M1KrGYWc86GQ96OqR6AU/Wg+a3DNcS6o18rIZbwwWt/Ffp5ni3JAxFzWi7J2g0Y9yv0oEgk/pPNJRqUc5OEQxlYS00mC6xwOhxN2uz3y+Sd0O1FcHBXLxRO0kh/Pja/QK2Fxz72z3+X5vIJUygOp0zGYUR26XsZg0IVhNClk4485joN+v4d220Cn00av27l6ryu8222L/4bRwmw2hWTbO1iWhXJZw36/x3q1FiIfbbPZwrZtPrT7J17X1+/1eo3j8QTJHD6SnxeZX5+ZaZTMyKSh4HIBzuczNK2IkhpATb9lDLLkEAqyjzFMfmGoSpD/QzAHMSSTPkjbTUY0QqX3ezFYozhqNRmn05kZH6AoObSNe3K7EyI6hV2RWjVChjdirVdusd1kkU4HmKFpkkOPWTVE6cOhKVL/aJY1xmhk/R/f164PBibRbCCtVnOK9FAs5slvgen0jYLHv2IX1j6fTwl8IqJ7frmci+j6YjETe+697XYL6dV65Fx5yOULBv0f5BhmOTLcvriZFgsZlurD5C1FJA/E44Gm+sX4qIoXrWYEL2ac45ZELPYJ0nolE26EIlG8jn+yjBSaTY0NcTiLR1SrJTKMs6MqRTPvZ+/J7Y4NfKBYGqtlATtbQTbzDb8BdIjNwJF+0gkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 14 07 48&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-14-07-48-394925941b22c1fdef88c20d5973368e-9cb4a.png&quot; data-srcset=&quot;/static/2024-11-20-14-07-48-394925941b22c1fdef88c20d5973368e-931d4.png 200w,
/static/2024-11-20-14-07-48-394925941b22c1fdef88c20d5973368e-3a665.png 400w,
/static/2024-11-20-14-07-48-394925941b22c1fdef88c20d5973368e-9cb4a.png 510w&quot; data-sizes=&quot;(max-width: 510px) 100vw, 510px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;针对上图，我们第一步需要讨论的是在服务调用过程中如何生成 Span。在 Spring Cloud Sleuth 中，生成 Span 的过程发生在对请求进行拦截的过程中，这个拦截器就是 LazyTracingFilter。&lt;/p&gt;
&lt;p&gt;我们来看位于该拦截器中与创建 Span 相关的实现方式，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;67071654274346980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
   HttpServletRequest req = (HttpServletRequest) request;
   HttpServletResponse res = servlet.httpServletResponse(response);

   // 从请求中获取现有 Span 或创建新的 Span
   Span span = handler.handleReceive(new HttpServletRequestWrapper(req));

   // 为 Span 添加额外属性
   request.setAttribute(SpanCustomizer.class.getName(), span);
   request.setAttribute(TraceContext.class.getName(), span.context());

   // 保存当前的 Span 信息，用于将这些信息通过过滤器链继续向下传递
   CurrentTraceContext.Scope scope = currentTraceContext.newScope(span.context());
   try {
      chain.doFilter(req, res);
   } catch (Throwable e) {
      //...
   } finally {
      // ...
      // scope.close();
   }
}`, `67071654274346980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletResponse&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterChain&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; req &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;HttpServletResponse&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; servlet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;httpServletResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 从请求中获取现有 Span 或创建新的 Span&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; span &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleReceive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServletRequestWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 为 Span 添加额外属性&lt;/span&gt;
   request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpanCustomizer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; span&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TraceContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 保存当前的 Span 信息，用于将这些信息通过过滤器链继续向下传递&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;CurrentTraceContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Scope&lt;/span&gt; scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTraceContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newScope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      chain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// scope.close();&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，Spring Cloud Sleuth 在底层实际上是借助于 Brave 这个框架来实现 Span 的创建和传递，所以这里出现了很多与这个框架相关的代码。&lt;/p&gt;
&lt;p&gt;考虑到在一个服务调用链路中，当前的服务消费者对于其他服务而言也可能是服务的提供者。所以，我们会首先判断 Brave 框架中当前的跟踪上下文 TraceContext 是否为空，如果不为空就说明这个服务消费者是其他服务的提供者，我们可以通过 Tracer 的 joinSpan 方法把当前已经存在的 Span 加入到该上下文中。反之则说明这个服务消费者是整个调用链路的第一个服务，我们会通过 Tracer 的 nextSpan 方法创建一个新的 Span，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50330502711456784000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Span nextSpan(TraceContextOrSamplingFlags extracted, HttpServerRequest request) {
   Boolean sampled = extracted.sampled();
   if (sampled == null &amp;&amp; (sampled = sampler.trySample(request)) != null) {
      extracted = extracted.sampled(sampled.booleanValue());
   }
   // 判断是否新建一个 Span
   return extracted.context() != null
         ? tracer.joinSpan(extracted.context())
         : tracer.nextSpan(extracted);
}`, `50330502711456784000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nextSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TraceContextOrSamplingFlags&lt;/span&gt; extracted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpServerRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Boolean&lt;/span&gt; sampled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; extracted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sampled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sampled &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sampled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sampler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trySample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      extracted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; extracted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sampled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sampled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;booleanValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 判断是否新建一个 Span&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; extracted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
         &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joinSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extracted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extracted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 Tracer 是 Brave 框架所提供的一个工具类，具备一组用于完成与 Span 相关的各种属性和操作的方法，我们在本讲后续内容中还会对该类做进一步展开讨论。&lt;/p&gt;
&lt;p&gt;当我们已经获取一个有效的 Span 对象之后，就可以对它做一些必要的赋值操作，然后保存这个 Span。这样，这个 Span 中的信息就可以通过过滤器链继续在调用链路中向下传递。这个过程也是通过一个拦截器来完成的，这个拦截器就是 TracingClientHttpRequestInterceptor，我们来看它的拦截实现方式，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;86568224220191780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public ClientHttpResponse intercept(HttpRequest req, byte[] body, ClientHttpRequestExecution execution) throws IOException {
   HttpRequestWrapper request = new HttpRequestWrapper(req);
   // 将 Span 信息注入到 HTTP 请求中
   Span span = handler.handleSend(request);

   ClientHttpResponse response = null;
   Throwable error = null;
   try (CurrentTraceContext.Scope ws = currentTraceContext.newScope(span.context())) {
      // 发送请求、获取响应
      response = execution.execute(req, body);
      return response;
   } catch (Throwable e) {
      // ...
   } finally {
      // 创建事件
      handler.handleReceive(new ClientHttpResponseWrapper(request, response, error), span);
   }
}`, `86568224220191780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpResponse&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpRequest&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpRequestExecution&lt;/span&gt; execution&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;HttpRequestWrapper&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpRequestWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 将 Span 信息注入到 HTTP 请求中&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; span &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleSend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;ClientHttpResponse&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; error &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CurrentTraceContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Scope&lt;/span&gt; ws &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTraceContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newScope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 发送请求、获取响应&lt;/span&gt;
      response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; execution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 创建事件&lt;/span&gt;
      handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleReceive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientHttpResponseWrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; span&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你可能会问，Span 信息是如何传递给下一个服务的呢？答案就在这个 &lt;code class=&quot;language-text&quot;&gt;HttpClientHandler.handleSend&lt;/code&gt; 方法中，该方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;10651275576358167000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Span handleSend(HttpClientRequest request, Span span) {
   // ...
   // 将 Span 信息注入到 HTTP 消息头
   defaultInjector.inject(span.context(), request);

   // 创建事件
   return handleStart(new HttpClientRequest.ToHttpAdapter(request), request.unwrap(), span);
}`, `10651275576358167000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleSend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpClientRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; span&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 将 Span 信息注入到 HTTP 消息头&lt;/span&gt;
   defaultInjector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 创建事件&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ToHttpAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; span&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里出现了一个注入器组件 Injector，该组件会将一个 Span 中相关的 SpanId、TraceId、ParentId 等信息注入到 HTTP 请求的消息头中，从而确保这些信息能够随着 HTTP 请求的发送传递到下一个服务中。&lt;/p&gt;
&lt;p&gt;请注意，这里出现的 handleReceive 和 handleStart 方法分别用于记录 Span 的关键事件。从命名上，我们不难理解 handleReceive 用于处理 cr 事件，而 handleStart 则用来处理 cs 事件。通过这种方式，当请求在某一个服务中被处理完成，相应的事件也会被记录下来。&lt;/p&gt;
&lt;h3 id=&quot;使用-brave-创建自定义-span&quot;&gt;&lt;a href=&quot;#%E4%BD%BF%E7%94%A8-brave-%E5%88%9B%E5%BB%BA%E8%87%AA%E5%AE%9A%E4%B9%89-span&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;使用 Brave 创建自定义 Span&lt;/h3&gt;
&lt;p&gt;在前面的内容中，我们已经提到了通过 Brave 框架来生成 Span 的实现方法。同样的，如果想要在访问链路中创建自定义的 Span，需要对 Brave 框架所提供的功能有足够的了解。&lt;/p&gt;
&lt;p&gt;我们首先来关注 Brave 中的 Span 类，该类的方法列表如下所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-20-14-25-04-230a789584923190762ff51c223c3d2f-c18ed.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 388px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 115.97938144329898%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsSAAALEgHS3X78AAAC10lEQVQ4y52VyZLaQBBE+f9f8c0/4JNPY0cwCNC+IxBC+8YyoHR2M8EQMDC2O6JpcdDrqqzK0uiH7+H7bwPffk5RbQ8Q6zQMGK62WNfPz9ao7LZYhT4UTYdpO5++dAt+CsTphGbXIAg9GLoGL/CR5xu0dYqqiNE2xR34GXwkfspdi2ARwDBMvCoTJBuCqhj5ZnGGEr7btndRfx4hV9aXCKMAuq5D1VR41HVDaJktCQ2Rrj0U6QIdo22b/CbC4R5Y7btzhEw5oJ5hYCFeL2VUArLta/QtYXX2F8BBpNzAdi1oBEbLCFkSoqkSvB32D8V/mnK+reF6DlPWMJvPMJ3NkGcJ0thhdNV/FIWpuUEA0zQIU2DZPAmuqgzJykbfN3z59L4fp3tJuWCElmtjNptiPB7DNDS8/HqBzgts24Ju6CgLRrx2sdt1X6ecdhVsz2XbGFJHVZ3DciyofC6KDapsgW1XfanfBdjIKvuMxib03DpCT9fzZPoC3DQlCrZQ3zUf0EcaZrzdtExMFEVqKJ5tEaGqIgg8rOMAaRKwQA2OxzcJelQYCRQaurScpmmEGbI4PptbmU5lg/f12S2PWucaOhK31bRenKzhOOeUxSnaR2dxQnrc8wysVgtkWS6j7NuKI+AoazzcTCZZ5awT1gtZEFUWxbJNqaGmzeX/OTUVcggpRKTZ2sF+3z8uSiWGwzKULSIAunFuF2WqEDaXp0afv07G0pJRaLL6Kbq+R9e1dNQOB15w4Pk+bRr49LLoN4P6GSZPpruIFnJQGJZFTX15WRhFlMCVVvVphjiOpE2LLEKZr96tR/N71Gr8OpbFaJsUdbn+1AlfTmzxU+9pPYLE+BIR2q7Lvivkrfv97s7Hz/YlZdt3KfxENvWMTknThLMwkKPrn4H5toLLlCcUP9kkSGKfkzqRw+DET8QFeCXBUy93hwMcOkJha4h5OAxHHN8OD1++/RJe7z+ZUuD8Q9o4VgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 20 14 25 04&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-20-14-25-04-230a789584923190762ff51c223c3d2f-c18ed.png&quot; data-srcset=&quot;/static/2024-11-20-14-25-04-230a789584923190762ff51c223c3d2f-a2138.png 200w,
/static/2024-11-20-14-25-04-230a789584923190762ff51c223c3d2f-c18ed.png 388w&quot; data-sizes=&quot;(max-width: 388px) 100vw, 388px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;注意到 Span 是一个抽象类，在上面的方法列表中，我们也看到该类的几乎所有方法都是抽象方法，需要子类进行实现。在 Brave 中，该抽象类的子类就是 RealSpan。RealSpan 中的 start 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;36138010988687540000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Span start(long timestamp) {
   synchronized (state) {
      state.startTimestamp(timestamp);
   }
   return this;
}`, `36138010988687540000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 state 是一个可变的 MutableSpan，而上述 start 方法就是为这个 MutableSpan 设置了开始时间。可以想象，对应的 finish 方法也会为 MutableSpan 设置结束时间，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58657566199658870000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public void finish(long timestamp) {
   if (!pendingSpans.remove(context)) return;
   synchronized (state) {
      state.finishTimestamp(timestamp);
   }
   finishedSpanHandler.handle(context, state);
}`, `58657566199658870000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;pendingSpans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;finishTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   finishedSpanHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于关闭 Span 的操作而言，上述方法还添加了一个 Handler 以便执行回调逻辑，这也是非常常见的一种实现技巧。&lt;/p&gt;
&lt;p&gt;我们接着来看另一个非常有用的 annotate 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37501941209174606000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Span annotate(long timestamp, String value) {
   if (&amp;quot;cs&amp;quot;.equals(value)) {
      synchronized (state) {
         state.kind(Span.Kind.CLIENT);
         state.startTimestamp(timestamp);
      }
   } else if (&amp;quot;sr&amp;quot;.equals(value)) {
      synchronized (state) {
         state.kind(Span.Kind.SERVER);
         state.startTimestamp(timestamp);
      }
   } else if (&amp;quot;cr&amp;quot;.equals(value)) {
      synchronized (state) {
         state.kind(Span.Kind.CLIENT);
      }
      finish(timestamp);
   } else if (&amp;quot;ss&amp;quot;.equals(value)) {
      synchronized (state) {
         state.kind(Span.Kind.SERVER);
      }
      finish(timestamp);
   } else {
      synchronized (state) {
         state.annotate(timestamp, value);
      }
   }
   return this;
}`, `37501941209174606000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;annotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLIENT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SERVER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLIENT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SERVER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;annotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;基于前面讨论的四种监控事件，我们不难理解上述代码的作用就是为这些事件指定类型以及时间，从而为构建监控链路提供基础。&lt;/p&gt;
&lt;p&gt;RealSpan 中最后一个值得介绍的方法是如下所示的 tag 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;68572174135391120000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Span tag(String key, String value) {
   synchronized (state) {
      state.tag(key, value);
   }
   return this;
}`, `68572174135391120000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;该方法为 Span 打上一个标签，其中两个参数分别代表标签的 Key 和 Value，开发人员可以根据需要对任何一个 Span 添加自定义的标签体系。&lt;/p&gt;
&lt;p&gt;了解了 Span 的定义之后，我们就来讨论如何在业务代码中创建自定义 Span 的方法，这时候就需要引入前面已经给出的 Tracer 类，我们同样挑选几个常见的方法进行展开。&lt;/p&gt;
&lt;p&gt;首先，我们来看如何通过 Tracer 创建一个新的根 Span，可以通过如下所示的 newTrace 方法进行实现：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69429172538445980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Span newTrace() {
   return _toSpan(newRootContext());
}`, `69429172538445980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_toSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newRootContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里用到了一个用于保存跟踪信息的 TraceContext 上下文对象，对于根 Span 而言，这个 TraceContext 就是全新的上下文，没有父 Span。而这里的 _toSpan 方法则最终构建了一个前面提到的 RealSpan 对象。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;55684581967235670000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Span _toSpan(TraceContext decorated) {
   if (isNoop(decorated)) return new NoopSpan(decorated);
   PendingSpan pendingSpan = pendingSpans.getOrCreate(decorated, false);
   return new RealSpan(decorated, pendingSpans, pendingSpan.state(), pendingSpan.clock(), finishedSpanHandler);
}`, `55684581967235670000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_toSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TraceContext&lt;/span&gt; decorated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decorated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NoopSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decorated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;PendingSpan&lt;/span&gt; pendingSpan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pendingSpans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decorated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RealSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decorated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pendingSpans&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pendingSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pendingSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; finishedSpanHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里多了一个新建的对象叫 PendingSpan，该对象用于收集一条 Trace 上暂时被挂起的未完成的 Span。&lt;/p&gt;
&lt;p&gt;一旦创建了根 Span，我们就可以在这个 Span 上执行 nextSpan 方法来添加新的 Span，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2976378180461503000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Span nextSpan() {
   TraceContext parent = currentTraceContext.get();
   return parent != null ? newChild(parent) : newTrace();
}`, `2976378180461503000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nextSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;TraceContext&lt;/span&gt; parent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTraceContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; parent &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里获取当前 TraceContext，如果该上下文不存在，就通过 newTrace 方法来创建一个新的根 Span；如果存在，则基于这个上下文并调用 newChild 方法来创建一个子 Span。newChild 方法也比较简单，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;65451963834714680000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Span newChild(TraceContext parent) {
   if (parent == null) throw new NullPointerException(&amp;quot;parent == null&amp;quot;);
   return _toSpan(nextContext(parent));
}`, `65451963834714680000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TraceContext&lt;/span&gt; parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NullPointerException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;parent == null&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_toSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当然，在很多场景下，我们需要获取当前的 Span，这时候就可以使用 Tracer 类所提供的 currentSpan 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45399291418430370000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Span currentSpan() {
   TraceContext currentContext = currentTraceContext.get();
   return currentContext != null ? toSpan(currentContext) : null;
}`, `45399291418430370000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;currentSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;TraceContext&lt;/span&gt; currentContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTraceContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; currentContext &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;基于 Tracer 提供的这些常见方法，我们可以梳理在业务代码中添加一个自定义 Span 的模版方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;76632165669479740000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Service
public class MyService {
   @Autowired
   private Tracer tracer;

   public void perform() {
      Span newSpan = tracer.nextSpan().name(&amp;quot;spanName&amp;quot;).start();
      try {
         // 执行业务逻辑
      } finally{
         newSpan.tag(&amp;quot;key&amp;quot;, &amp;quot;value&amp;quot;);
         newSpan.annotate(&amp;quot;myannotation&amp;quot;);
         newSpan.finish();
      }
   }
}`, `76632165669479740000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Tracer&lt;/span&gt; tracer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Span&lt;/span&gt; newSpan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spanName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 执行业务逻辑&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         newSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         newSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;annotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;myannotation&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         newSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上述代码中，我们注入了一个 Tracer 对象，然后通过 nextSpan 方法创建并启动了一个名称为“spanName”的新 Span。这是在业务代码中嵌入自定义 Span 的一种常用实现方法。当我们执行完各种业务逻辑之后，可以分别通过 tag 方法和 annotate 方法添加标签和定义事件，最后通过 finish 方法关闭 Span。这段模板代码可以直接引入到日常的开发过程中。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-17&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;构建服务监控和链路跟踪在分布式系统开发过程中是一项基础设施类工作，而我们可以借助于类似 Spring Cloud Sleuth 这样的框架来轻松完成这项工作。Spring Cloud Sleuth 内置了日志采集和分析机制，能够帮忙我们自动建立 TraceId 和 SpanId 之间的关联关系。对于这些工具和框架的介绍是应对这类面试题的第一个要点，也是最基本的要求。在日常开发过程中，很多开发平台已经帮我们内置了对这些工具的支持，但我们还是需要多接触一下这方面的技术组件。&lt;/p&gt;
&lt;p&gt;就面试官的考查方式而言，先从框架应用开始聊起，然后再切入到框架基本原理的讨论是一种常见的提问方式。因此，我们需要对具体框架中所涉及的实现原理做进一步展开。&lt;/p&gt;
&lt;p&gt;这里有两个要点，一方面我们需要介绍分布式服务跟踪的设计思想和基本概念，这部分包括如何基于 Trace 和 Span 构建服务链路的整个过程。另一方面，我们需要结合具体的框架来分析这些概念如何在现实中进行落地，这部分可以重点对本讲中介绍的 Brave 框架进行讨论。&lt;/p&gt;
&lt;p&gt;最后，关于链路跟踪，我们还可以介绍与可视化相关的一些话题，例如 Zipkin 的基本架构、Spring Cloud Sleuth 如何与 Zipkin 进行集成等。作为加分项，这些内容可以展示候选人所具备的知识体系。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-15&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;链路跟踪是一个理论和实践紧密结合的综合性话题，本讲内容对这个话题背后所涉及的核心概念和基本原理都做了详细的介绍。同时，我们基于 Spring Cloud 中的 Spring Cloud Sleuth 以及业界主流的 Brave 框架分析了对应的实现方式。&lt;/p&gt;
&lt;p&gt;讨论完服务监控之后，下一讲我们将讨论消息通信机制。在分布式系统中，消息通信也是一个基础的技术组件，业界也存在一批不同的消息中间件，这些中间件在使用方式上不尽相同。那么，作为基础设施类组件，我们如何设计跨消息中间件的统一消息通信平台？这是下一讲要展开的内容。&lt;/p&gt;
&lt;h1 id=&quot;消息通信：如何设计跨消息中间件的统一消息通信平台？&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E6%81%AF%E9%80%9A%E4%BF%A1%EF%BC%9A%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E8%B7%A8%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%9A%84%E7%BB%9F%E4%B8%80%E6%B6%88%E6%81%AF%E9%80%9A%E4%BF%A1%E5%B9%B3%E5%8F%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消息通信：如何设计跨消息中间件的统一消息通信平台？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们解决了分布式系统构建过程中的一个重要问题，即如何对整个服务链路进行有效的监控。基于 Spring Cloud Sleuth 框架，我们发现可以利用 Brave、Zipkin 等一组业务主流的第三方组件来实现这一目标。&lt;/p&gt;
&lt;p&gt;在本讲内容中，我们也将采用类似的讲解思路，来对 Spring Cloud 中另一款强大的框架展开讨论，这款框架就是 Spring Cloud Stream。&lt;/p&gt;
&lt;p&gt;Spring Cloud Stream 是 Spring 家族提供的消息通信框架，而消息通信也是我们在分布式系统构建过程中的一个重要技术组件。然而，和普通的消息中间件不同，Spring Cloud Stream 为开发人员提供的是一种跨消息中间件的统一消息通信平台。&lt;/p&gt;
&lt;p&gt;那么，它是如何做到这一点的呢？这是一个很好的面试题，本讲内容将围绕这一问题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-15&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-15&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;我们知道，在分布式系统设计和开发过程中，服务与服务之间可以通过 RPC 实现交互。但是，RPC 虽然实现起来比较简单，但却是一种耦合度较高的实现技术。为了降低服务与服务之间的耦合度，有时候我们需要引入消息通信机制，采用异步的方式来完成不同服务之间的交互。&lt;/p&gt;
&lt;p&gt;讲到这里，你可能会说，这不就是消息中间件能解决的问题吗？答案是肯定的，但还不够。&lt;/p&gt;
&lt;p&gt;我们知道常见的消息通信规范以及中间件有很多种，代表性的规范有 JMS 和 AMQP，对应的实现框架包括 ActiveMQ 和 RabbitMQ 等，而 Kafka、RocketMQ 等工具并不遵循特定的规范但也提供了消息通信的实现方案。显然，这些中间件的使用方式是完全不同的。&lt;/p&gt;
&lt;p&gt;那么，如何屏蔽这些中间件在使用上的差别，从而为开发人员提供一套统一且高效的消息发送和接收 API，这是我们今天要讨论的问题，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-15-03-5a80b036e605d9496845798acd67701c-d6910.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 568px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.47887323943662%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABvUlEQVQoz2WQCW/aUBCE/f9/RJASKaGVglJU4kIwVUHFEAjExDimgB1ISDnFIW5jvj6bIygdad+1s/NmV0Kg3+9SLoexbZl8PoimXdNsRik+hlkslh4F13X93TRzghfh2fhGLhekrN/4d8P47eclbxkMelSrUSGikM1+FaLXvNhRKhVFCG05RaNp+B/repiM+oWnUphWS+FP5X4nuN3uCubzBbPZgtVqzXrt0Ov1MY1n+t0ui9mMuYjxaIRVq9N6e/ede7zlcsV0OsdxNh+CB9FT9IdDbo0ysmnyq9kgYdvc1WqECgU6Iufhc5WnIx0OhzjOyrJITKfEhStZuLxpCNHxGHkwoPL6epzraa3v0Ht0t/vYEzyMJhNS+hP5eo2CbfFg1Sk2bJIljcHeobs3cKwXIf3XqxjFZrXB3Ww+Wlk6OLPl8e6I2blr1+d+hqSaKhk7Q7qWFnuWaDFK8jlJu9shW71HtVRi2h1y4Yd/zll52p02KSNFXI/7dWpdaLxkRBcPSCE9ROTtO+e5cwJqgKvSFfFOnEdDI1K7JVgO7nLpAJfaJcpfhaJeRG7KKEOFs/QZF/kLfk4SxBox/gGEEJTWz8U6EQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 15 03&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-15-03-5a80b036e605d9496845798acd67701c-d6910.png&quot; data-srcset=&quot;/static/2024-11-21-14-15-03-5a80b036e605d9496845798acd67701c-487ad.png 200w,
/static/2024-11-21-14-15-03-5a80b036e605d9496845798acd67701c-1cefb.png 400w,
/static/2024-11-21-14-15-03-5a80b036e605d9496845798acd67701c-d6910.png 568w&quot; data-sizes=&quot;(max-width: 568px) 100vw, 568px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在面试过程中，我也经常拿这个问题来考查候选人，发现大家都没有很好的回答思路，可以说这是一个比较有挑战的话题。围绕这个话题，我们还可以有以下几种提问方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消息通信机制如何实现服务与服务之间的解耦？&lt;/li&gt;
&lt;li&gt;如果让你设计一套统一的消息发送和消费的 API，你会怎么考虑？&lt;/li&gt;
&lt;li&gt;Spring Cloud Stream 的基本架构是怎么样？&lt;/li&gt;
&lt;li&gt;Spring Cloud Stream 与 Spring Integration 之间是什么关系？&lt;/li&gt;
&lt;li&gt;Spring Cloud Stream 是如何完成与 Kafka、RocketMQ 等不同中间件之间的无缝集成？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述问题的考查点并不在于某一个消息中间件，而是站在通用性和统一性的角度来看问题，所以难度很大，需要候选人具备较强的抽象能力。而通过学习 Spring Cloud Stream 这种优秀的开源框架，可以帮助你构建这种抽象能力。&lt;/p&gt;
&lt;p&gt;接下来，让我们再对这些问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-16&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;想要回答这类面试题，首先我们需要对消息中间件本身有足够的了解，这是设计跨消息中间件的统一消息通信平台的前提。在本讲中，这部分内容我们不会做重点展开，需要你事先做一些学习。&lt;/p&gt;
&lt;p&gt;围绕统一消息平台的设计和实现，我们接下来要明确的是所采用的技术体系。目前，针对这个主题，我们可以参考和借鉴的框架并不多，而 Spring 家族中的 Spring Cloud Stream 是其中的代表。&lt;/p&gt;
&lt;p&gt;Spring Cloud Stream 对整个消息发布和消费过程做了高度抽象，并提供了一系列核心组件，包括 Binder、Channel、Source 和 Sink 等。&lt;/p&gt;
&lt;p&gt;最后，让我们基于具体框架来分析底层的实现原理。在 Spring Cloud Stream 中，真正完成与对不同消息中间件之间的集成的是 Binder 组件。而不同的消息中间件具有不同的 API，所以在 Binder 组件的设计和实现过程中，一方面需要考虑抽象，另一方面也需要针对不同中间件的特性来完成底层的交互过程。这部分内容是我们需要掌握的重点，值得深入进行学习，对于自身架构设计能力的提升有很大帮助。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-18&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;h3 id=&quot;spring-家族中的消息通信解决方案&quot;&gt;&lt;a href=&quot;#spring-%E5%AE%B6%E6%97%8F%E4%B8%AD%E7%9A%84%E6%B6%88%E6%81%AF%E9%80%9A%E4%BF%A1%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring 家族中的消息通信解决方案&lt;/h3&gt;
&lt;p&gt;在 Spring 家族中，与消息通信机制相关的框架有三个，分别是 Spring Messaging、Spring Integration 和 Spring Cloud Steam。&lt;/p&gt;
&lt;p&gt;事实上，Spring Cloud 中的 Spring Cloud Stream 是基于 Spring Integration 实现了消息发布和消费机制并提供了一层封装，很多关于消息发布、消费的概念和实现方法本质上都是依赖于 Spring Integration。而在 Spring Integration 的背后，则依赖于 Spring Messaging 组件来实现消息处理机制的基础设施。&lt;/p&gt;
&lt;p&gt;这三个框架之间的依赖关系如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-16-14-1a63673ad172aaddb8f0e0ebc8b1b752-72b6c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 527px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 67.93168880455408%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACmUlEQVQ4y3VT+VPaUBDmz7c/tnQ60gPBe0QIEM5wM1ZbCGgEBAFxdOQSvAhHwvV184hYa92Znd23u+97X3Y3htl8Dk1a7TY2wmHsp1Kwp1Pgshk4j4+Zb/t1BBfz08zXctpZs1r9ZjSCRqvJcAyz2Yw5pYsaPM0WTsnfkiQ4azU4KlV4bm7gJXVUKvDW67CVzmE/P4etWAJHd8pU77+9RaFS1gF1hk2NYTQKhyjC5HLhh9+PLxzH7DrFv3k8sAgC1iMRmNw8qRvWUIhYZrEVj6He1BnOCXCug84mE4wVhfntVgt9WV7GqQiz6ZSd77pdPD09Ml+rZ3mA4RjwjnTv7qCO1f/mVFVFU2f0LM+kDFNliqlKqkwwGdFLkznub++R+Z2B0leg5bU4q1OmSwANcKoz/vsrDUJWQFSKIp6PI0aaKCQQEANY49eQOEuyOMudxlj8SDpabAW1ZDQavQUsogjukoOjxsF9zSPQCcDT8NIEOYo7sV+1w0k2/BBGDnm48248dB4wUkbodDqvPpf1kK/SxAQTjC4jTAGybiPMMTM+cR+xsrmCz7wRq75VWJIWcCUOzkMn1KGK8WSMRqPxlmGXJtbr9SD3ZfTkHtNHmmB/IKPT7eDsrMDscDiAVjscDJZ9bLdb1EdtwgtAbaffnfLL9IBGvY1a7WoZ0/8FFAolXF5ev6pf7uHzC5pcXV0gmdxHNutDLhdCJuOFz2dGOs0jdypAkoIEFkE8votIZBsnJ0GkUjwODsIvDBeAizWQpJ+Q5RAGgyQyohXVip2YuFAq7kFMW5HP7xDgDopFGy5oeKp6QLdysFg//Au4YFgu5yCKeyjkefj933F4uIsA2UR8A7HYOtNg0Ayv9ysEwUy1dlSrtB2ebfwBCy3xmDWEztgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 16 14&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-16-14-1a63673ad172aaddb8f0e0ebc8b1b752-72b6c.png&quot; data-srcset=&quot;/static/2024-11-21-14-16-14-1a63673ad172aaddb8f0e0ebc8b1b752-96efc.png 200w,
/static/2024-11-21-14-16-14-1a63673ad172aaddb8f0e0ebc8b1b752-b717e.png 400w,
/static/2024-11-21-14-16-14-1a63673ad172aaddb8f0e0ebc8b1b752-72b6c.png 527w&quot; data-sizes=&quot;(max-width: 527px) 100vw, 527px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从依赖关系上讲，Spring Messaging 是 Spring 家族中处理消息通信的底层框架。而 Spring Integration 在定位上属于一种企业服务总线，依赖于 Spring Messaging。因此，我们先来介绍 Spring Messaging。&lt;/p&gt;
&lt;p&gt;Spring Messaging 是 Spring 框架内置的一个模块，提供了最基本的消息通信 API，其中，消息这个概念由 Message 接口进行表示，包括一个消息头 Header 和一个消息体 Payload，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74281030153734370000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Message&lt;T&gt; {
   T getPayload();
   MessageHeaders getHeaders();
}`, `74281030153734370000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPayload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;MessageHeaders&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而消息通道 MessageChannel 的定义也比较简单，只包含了一个用来发送消息的 send 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;9478162230428766000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface MessageChannel {
   long INDEFINITE_TIMEOUT = -1;

   default boolean send(Message&lt;?&gt; message) {
      return send(message, INDEFINITE_TIMEOUT);
   }

   boolean send(Message&lt;?&gt; message, long timeout);
}`, `9478162230428766000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; INDEFINITE_TIMEOUT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; INDEFINITE_TIMEOUT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Spring Messaging 把通道抽象成两种基本的表现形式，即支持轮询的 PollableChannel 和实现发布-订阅模式的 SubscribableChannel，这两个通道都继承自具有消息发送功能的 MessageChannel，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32308769057962380000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface PollableChannel extends MessageChannel {
   Message&lt;?&gt; receive();
   Message&lt;?&gt; receive(long timeout);
}

public interface SubscribableChannel extends MessageChannel {
   boolean subscribe(MessageHandler handler);
   boolean unsubscribe(MessageHandler handler);
}`, `32308769057962380000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PollableChannel&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubscribableChannel&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意到对于 PollableChannel 而言才有 receive 的概念，代表通过轮询操作主动获取消息的过程。而 SubscribableChannel 则是通过注册回调函数 MessageHandler 来实现事件响应。MessageHandler 接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;86600668649601250000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface MessageHandler {
   void handleMessage(Message&lt;?&gt; message) throws MessagingException;
}`, `86600668649601250000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessagingException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Spring 家族中，Spring Integration 是对 Spring Messaging 的扩展，提供了对《企业集成模式：设计、构建及部署消息通信解决方案》一书中各种企业集成模式的支持，通常被认为是一种 ESB（Enterprise Service Bus，企业服务总线）框架。而 Spring Cloud Stream 则是 Spring Integration 的一种增强。&lt;/p&gt;
&lt;p&gt;我们先来看一下 Spring Cloud Stream 中与 Spring Integration 相关的内容。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Stream 中，存在一组 Source 和 Sink 接口，其中 Source 接口的定义如下所示。注意到这里通过 Spring Messaging 提供的 MessageChannel 来对外发送消息。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88426440272981460000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Source {
   String OUTPUT = &amp;quot;output&amp;quot;;

   @Output(Source.OUTPUT)
   MessageChannel output();
}`, `88426440272981460000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Source&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; OUTPUT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OUTPUT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;类似的，Sink 接口定义如下所示，通过 Spring Messaging 中的 SubscribableChannel 实现对来自外部消息的接收。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92822886578620700000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Sink {
   String INPUT = &amp;quot;input&amp;quot;;

   @Input(Source.INPUT)
   SubscribableChannel input();
}`, `92822886578620700000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Sink&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; INPUT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;INPUT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;SubscribableChannel&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;spring-cloud-stream-基本架构&quot;&gt;&lt;a href=&quot;#spring-cloud-stream-%E5%9F%BA%E6%9C%AC%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Stream 基本架构&lt;/h3&gt;
&lt;p&gt;Spring Cloud Stream 对整个消息发布和消费过程做了高度抽象，并提供了一系列核心组件。我们先来介绍基于 Spring Cloud Stream 构建消息通信机制的基本工作流程。&lt;/p&gt;
&lt;p&gt;区别于直接使用 RabbitMQ、Kafka 等消息中间件，Spring Cloud Stream 在消息生产者和消费者之间添加了一种桥梁机制，所有的消息都将通过 Spring Cloud Stream 进行发送和接收，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-18-12-fc0883fec43962211685fe7138f34f04-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 45.6656346749226%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9UlEQVQoz22Re1PaUBDF8/0/Qf/qdJTaKRgML+toVQj4CCGBRELCOyAveUhrH1op+fUSW+tMuzM7d++9e86e3ZWWywWuq+A4MTQtQqu9R72RZbVas7EgCP518d5oqnhelGIxglPbxfUUZvMJ0urhkenYY3ht02kaLGYei5seIeo34bMFf++LaZ/5jYtlZhn0LBE3uP/6HcnsmVRGFoZvknfyWKMrSgMDf+o/E/zxdfCkevOnX5eoDG0umheY12XMQYXyqIy0W41x1ZbRy9t8VF9Rb+5S6EQ59y8JHl+oe2GXguzE26HViZPXXuM0YtiNKEfTA6QPnX3OXJmCHUW136NVZbLdBKaotiH8sXoUvgr9/uEhJLQmNkcNOczNC5zmxik4QshdHilVT9K/PcEfHVJtJRjPTrBmhxhDk+X8E2/UHBG9xLZW5K2uM57MqAhCfZgOc6vtJO3hAb35MeryFGm/laFQi6GW33GobXEuVB634uj9EtPRFMvv4k4muOMxznDAl7tvXLQ1EsYWl1eiTYFRyzvkRax+ziElvQT1fga7JnOmR+j4GbR+kqIg5Od/RygWYlJoyrS76RDj1PeodZLkboVCpaaQ6qVJdVOk/c2ZRvEVjL4RznCz2XW44YDV+qmC0TOQRRdpUTwjxGzwSkshu8zxC/+5f2ng38J4AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 18 12&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-18-12-fc0883fec43962211685fe7138f34f04-0e173.png&quot; data-srcset=&quot;/static/2024-11-21-14-18-12-fc0883fec43962211685fe7138f34f04-2fb9f.png 200w,
/static/2024-11-21-14-18-12-fc0883fec43962211685fe7138f34f04-f1e72.png 400w,
/static/2024-11-21-14-18-12-fc0883fec43962211685fe7138f34f04-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们不难看出 Spring Cloud Stream 具备四个核心组件，分别是 Binder、Channel、Source 和 Sink，其中 Binder 和 Channel 成对出现，而 Source 和 Sink 分别面向消息的发布者和消费者。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Source 和 Sink&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Stream 中，Source 组件是真正生成消息的组件，相当于是一个输出（Output）组件。而 Sink 则是真正消费消息的组件，相当于是一个输入（Input）组件。根据我们对事件驱动架构的了解，对于同一个 Source 组件而言，不同的服务可能会实现不同的 Sink 组件，分别根据自身需求进行业务上的处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Channel&lt;/p&gt;
&lt;p&gt;Channel 的概念比较容易理解，就是常见的通道，这里不再展开。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Binder&lt;/p&gt;
&lt;p&gt;Spring Cloud Stream 中最重要的概念就是 Binder。所谓 Binder，顾名思义就是一种黏合剂，将业务服务与消息通信系统黏合在一起。通过 Binder，我们可以很方便地连接消息中间件，可以动态地改变消息的目标地址、发送方式，而不需要了解各种消息中间件在实现上的差异。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关于 Binder 是如何与不同的消息中间件进行整合的实现原理，我们在接下来的内容中进行详细展开。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-16&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;spring-cloud-stream-中的-binder&quot;&gt;&lt;a href=&quot;#spring-cloud-stream-%E4%B8%AD%E7%9A%84-binder&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Stream 中的 Binder&lt;/h3&gt;
&lt;p&gt;基于 Spring Cloud Stream，我们知道在发送和接收消息时，需要使用 @EnableBinding 注解。我们可以在 @EnableBinding 注解中指定一个 Source 或 Sink 接口。&lt;/p&gt;
&lt;p&gt;在 Spring Cloud Stream 中，BindableProxyFactory 是一个用于初始化由 @EnableBinding 注解所提供接口的工厂类，该类的定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;97113576102837060000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class BindableProxyFactory implements MethodInterceptor, FactoryBean&lt;Object&gt;, Bindable, InitializingBean`, `97113576102837060000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BindableProxyFactory&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MethodInterceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FactoryBean&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Bindable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InitializingBean&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意到 BindableProxyFactory 同时实现了 MethodInterceptor 接口和 Bindable 接口。其中，前者是 AOP 中的方法拦截器，而后者是一个标明能够绑定 Input 和 Output 的接口，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-19-48-3eabbef76133444d81dc72a86b93d92c-42472.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 468px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.794871794871796%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABq0lEQVQoz31Si07CQBDs//+ABo0aSYxGDRSM+EAqApVaKC9ReZSXlkqlVNRiQOm4d0aJEd1kspfr7ezMdoWUvIt2+wT5fAjZbICjVAyj0Tii8z5Go1ew8DyPg8Vw+ARVFVEoiPy9pgVRKoWIYx/C1ZWIB+sY5cttKIofWnYDlZsARm4ChhHH84v7i7DftwlnaDZEyKk1JBMrMM0INUpDiEaXETtZhCyv4lxeg3rh5zl9vopcLgjXHc0lbLUOUansIJlcwankg14PkNNjCIqyC9tOkpoYul2Jo9OJ0scDbsGdY/nxcYhUaou/MwyJcIpbOpfLhxBsewDLeiDbfZ5Z97s7g6xrcJwBoyKi6TdYjMdjUp9H1zB5Te/eQq9nYTBwIOCfeHtjZD/vJpMpEb7/WSN8WWGYTj8VOI6NaiWDhp6jOamo1zW0WwXKWVSrKprNHI3khjdjqlndpwOPEeJ7Rl+EptmiP+dDPO5DIrEESVqg9Vjn50zGDyW9hGLxAO9c6Gy2XOFM7Iyw09Fptzah63u0QkFSukfKRNSqIdRqYVxfb1ODCNhzDzOHDB+vUZJ8yu44DAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 19 48&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-19-48-3eabbef76133444d81dc72a86b93d92c-42472.png&quot; data-srcset=&quot;/static/2024-11-21-14-19-48-3eabbef76133444d81dc72a86b93d92c-4d5a2.png 200w,
/static/2024-11-21-14-19-48-3eabbef76133444d81dc72a86b93d92c-65125.png 400w,
/static/2024-11-21-14-19-48-3eabbef76133444d81dc72a86b93d92c-42472.png 468w&quot; data-sizes=&quot;(max-width: 468px) 100vw, 468px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们先来看 MethodInterceptor 接口中用于实现拦截的 invoke 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;16911833936764210000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public synchronized Object invoke(MethodInvocation invocation) throws Throwable {
   Method method = invocation.getMethod();

   // 从缓存中获取目标对象
   Object boundTarget = targetCache.get(method);
   if (boundTarget != null) {
      return boundTarget;
   }

   // 获取 Input 接口
   Input input = AnnotationUtils.findAnnotation(method, Input.class);
   if (input != null) {
      String name = BindingBeanDefinitionRegistryUtils.getBindingTargetName(input, method);
      boundTarget = this.inputHolders.get(name).getBoundTarget();
      targetCache.put(method, boundTarget);
      return boundTarget;
   }
   // 获取 Output 接口，和获取 Input 接口实现方式类似
   else {
      // ...
   }
   return null;
}`, `16911833936764210000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MethodInvocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 从缓存中获取目标对象&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; boundTarget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; targetCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boundTarget &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; boundTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 获取 Input 接口&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnnotationUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BindingBeanDefinitionRegistryUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBindingTargetName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      boundTarget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputHolders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBoundTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      targetCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundTarget&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; boundTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取 Output 接口，和获取 Input 接口实现方式类似&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的逻辑比较简单，可以看到 BindableProxyFactory 保存了一个缓存对象 targetCache，如果所调用方法已经存在于缓存中，则直接返回目标对象。反之，会根据 @Input 和 @Output 注解从 inputHolders 和 outputHolders 中获取对应的目标对象并放入缓存中。至于这里提到的这个目标对象，暂时可以把它理解为就是一种 MessageChannel 对象。&lt;/p&gt;
&lt;p&gt;然后我们来看 Bindable 接口，该接口提供了对 Input 和 Output 的绑定和解绑操作，而这些操作是通过 Binder 接口来完成的。Binder 接口分别提供了绑定生产者和消费者的方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84104479590467010000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Binder&lt;T, C extends ConsumerProperties, P extends ProducerProperties&gt; {
   // 绑定生产者
   Binding&lt;T&gt; bindProducer(String name, T outboundBindTarget, P producerProperties);

   // 绑定消费者
   Binding&lt;T&gt; bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
}`, `84104479590467010000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Binder&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;C&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumerProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;P&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProducerProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 绑定生产者&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bindProducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; outboundBindTarget&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;P&lt;/span&gt; producerProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 绑定消费者&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bindConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; group&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; inboundBindTarget&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;C&lt;/span&gt; consumerProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Spring Cloud Stream 中，Binder 接口的类层关系如下所示，注意到这里还展示了 RabbitMessageChannelBinder 类，这个类在接下来讲到 Spring Cloud Stream 与 RabbitMQ 的集成过程时会具体展开。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-115a3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 13.48314606741573%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAIAAAAcOLh5AAAACXBIWXMAAAsSAAALEgHS3X78AAAAkklEQVQI1x2MSQ7CMBRDe/+LZS6opD9zGghVM6x6A77wxk+25UWt6/p8MEqVkijGOOeCUiIEF1IhSIkkMCSEIOxaCyEYYzheALQxZgddz5qPbK1x3oXoY4rGGh/c+10AwDqUDTFgtb22fKQx2nLO03sHxsCu7b/Go5TSfd+ttz5a62hXn721a85Rv7WUUusn5/QD62qXyEmnXz4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 20 30&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-fee1c.png&quot; data-srcset=&quot;/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-a67b7.png 200w,
/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-0b187.png 400w,
/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-fee1c.png 800w,
/static/2024-11-21-14-20-30-b0af608facbad238082da8d8bd1e40a2-115a3.png 801w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;针对 Binder 接口的实现过程，Spring Cloud Stream 首先提供了一个 AbstractBinder，这是一个抽象类。AbstractBinder 的子类是 AbstractMessageChannelBinder，它同样也是一个抽象类。我们来看它的 doBindProducer 方法，并对该方法中的核心语句进行提取和整理，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74955475965075650000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public final Binding&lt;MessageChannel&gt; doBindProducer(final String destination, MessageChannel outputChannel, final P producerProperties) throws BinderException {
   // …
   // 发送到 outputChannel 通道的消息会被 SendingHandler 进行处理
   ((SubscribableChannel) outputChannel).subscribe( new SendingHandler(producerMessageHandler, ...);

   // 基于 outputChannel 构建 Binding
   Binding&lt;MessageChannel&gt; binding = new DefaultBinding&lt;MessageChannel&gt;(destination, null, outputChannel, producerMessageHandler instanceof Lifecycle ? (Lifecycle) producerMessageHandler : null) {
      // …
   };

   return binding;
}`, `74955475965075650000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doBindProducer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; destination&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt; outputChannel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;P&lt;/span&gt; producerProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BinderException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 发送到 outputChannel 通道的消息会被 SendingHandler 进行处理&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SubscribableChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; outputChannel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SendingHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;producerMessageHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 基于 outputChannel 构建 Binding&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Binding&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; binding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultBinding&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputChannel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; producerMessageHandler &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lifecycle&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Lifecycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; producerMessageHandler &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; binding&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码的核心逻辑在于，通过 Source 发送的消息会被 outputChannel 这个通道传递出去，而负责处理这些消息的是 SendingHandler，它是一个 Spring Messaging 模块中的 MessageHandler。&lt;/p&gt;
&lt;p&gt;这里要注意的是，SendingHandler 会使用 Spring Messaging 组件中的 Message 消息对象，而 Spring Cloud Stream 会把这个 Message 消息对象转换成对应中间件的消息数据格式并进行发送。&lt;/p&gt;
&lt;p&gt;下面转到消息消费的场景。针对消息消费，我们可以使用 @StreamListener 注解。如果在一个方法上添加了 @StreamListener 注解，那么这个方法就可以用来接收消息，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37497431198160806000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@StreamListener(&amp;quot;input&amp;quot;)
public void handleMessage(MyMessage myMessage)`, `37497431198160806000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@StreamListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MyMessage&lt;/span&gt; myMessage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;基于 @StreamListener 注解，在 Spring Cloud Stream 中存在一个 StreamListenerMessageHandler 类，用于订阅 inputChannel 消息通道中传入的消息并进行消费。&lt;/p&gt;
&lt;p&gt;作为总结，我们可以用如下所示的流程图来概括整个消息发送和消费流程：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-21-14-21-49-6cee773fc528711e5659a0000db69108-f1382.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 533px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 93.05816135084427%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAADL0lEQVQ4y42U+VPbVhSF+f+nIbQ/tZMQmras05mOSWxsgvEqjxcwliyDVyEvWm0HgyGQMfLXJ9FSKNDmzZy570pPZ86971wthKsRYkaMteIa66X1IG6WN9k43AiwWlwlrIWJGlFSjRTezMNf8/n8WSwoAwVlrCA1JRLVBCk1RbgoCA6ixOU4+/I+R9ZRcKZhNZh783vC59bC1fSKsTtmcnbO9GLKzc0NpmEyHA7p6l1cx2XyecLIGXH95fo/1QUKQ9UQsWGMXXeXreMtfkr+yJv0mwBvs2+JGBGiVpSIE2Gvvsfs6wzxKZ7n4f1F8lDxQttsUbfrNJ0GtX4NWZOp6SqH9RLqqYw+7ghoaOM29pn5bJkPy1947oAtSjzpmaQPFFqGS7Pv0Bw4NAY2Vb1HpX2K2u2jW1ag9JFCb+5L97j1boMHlusQtV0ynQJ1fRepEqKgfiCvfgz2pVoEzUySa2WI2w6j8fgxIcHmn16Yto0kmp8zTihrabalbbazIdbCvxBK/0GsuIPcy5HrVshNL3Fc91+EPG6q6ThkLq/I+v3sZlEGBQqtFMmjMLmT/SCXdQlJLyOdX+AOnxA+bqwlCFMXl5TMKsZZSVzIAeVmnGJth9JJLMgHZwccDspkJ+f/T+iXnPUVitvPtXLsqWnSDYlQJkTyOEOiliGv5cn0akgX36DQLzl9+YWSXqDd/kAi8yv76fcks6ukpd/I5tfpD3Y5aCUF4fTbCOOjIZmbr/zebLIqy6xWZDZUlc3aMSuFAtvdHqnra+KOfU94Z3TvKeFsNqNhDITRbWH0HqquU+8NaPRN4UULVdNROqd03BG67T6d5Zccf7+EP41x/w6f+4yuHKotmYZ+jH1uojnaHeyOgPZUoR/9Gf3b6J1+h6gjZn34iZX8Csu5ZYF3QfTz9/mf2aptkThP8MnZfX70eDD0t7e3uCMXdzwUDrAYiHZYjkVFrtBsN1FrKrZrMxTvnaHzAuEDxZPpBNVSqfQrKKZCVfhTMRSKnSLlXpmSVgreyYbMyaj+MqF/Y/7SDZ3l/DsWw695HVliaWeJxY+LvPogIOJ326+C/PvYD2yK39+f3HVuYyEfJ68AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 21 14 21 49&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-21-14-21-49-6cee773fc528711e5659a0000db69108-f1382.png&quot; data-srcset=&quot;/static/2024-11-21-14-21-49-6cee773fc528711e5659a0000db69108-7cec2.png 200w,
/static/2024-11-21-14-21-49-6cee773fc528711e5659a0000db69108-dfa51.png 400w,
/static/2024-11-21-14-21-49-6cee773fc528711e5659a0000db69108-f1382.png 533w&quot; data-sizes=&quot;(max-width: 533px) 100vw, 533px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;到目前为止，Spring Cloud Stream 通过 Binder 组件分别完成了对 RabbitMQ 以及 Kafka 的集成。在接下来的内容中，我们将以 RabbitMQ 为例，给出 Spring Cloud Stream 集成 RabbitMQ 的实现过程。&lt;/p&gt;
&lt;h3 id=&quot;spring-cloud-stream-集成-rabbitmq&quot;&gt;&lt;a href=&quot;#spring-cloud-stream-%E9%9B%86%E6%88%90-rabbitmq&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Cloud Stream 集成 RabbitMQ&lt;/h3&gt;
&lt;p&gt;Spring Cloud Stream 团队提供了 spring-cloud-stream-binder-rabbit 作为与 RabbitMQ 集成的代码工程。这个代码工程只有四个类，我们需要重点关注的就是实现了 AbstractMessageChannelBinder 中几个抽象方法的 RabbitMessageChannelBinder 类。&lt;/p&gt;
&lt;p&gt;首先我们找到 RabbitMessageChannelBinder 中的 createProducerMessageHandler 方法，我们知道该方法用于完成消息的发送。我们在 createProducerMessageHandler 中找到了以下核心代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34913244777184715000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`final AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(buildRabbitTemplate(producerProperties.getExtension(), errorChannel != null));
endpoint.setExchangeName(producerDestination.getName());`, `34913244777184715000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AmqpOutboundEndpoint&lt;/span&gt; endpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AmqpOutboundEndpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildRabbitTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;producerProperties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; errorChannel &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
endpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setExchangeName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;producerDestination&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;首先，在 buildRabbitTemplate 方法中，我们看到了 RabbitTemplate 的构建过程。RabbitTemplate 是 Spring Amqp 组件提供的专门用于封装与 RabbitMQ 底层交互 API 的模板类。在构建 RabbitTemplate 的整个过程中，涉及到设置与 RabbitMQ 相关的 ConnectionFactory 等众多参数。&lt;/p&gt;
&lt;p&gt;然后，我们发现 RabbitMessageChannelBinder 也是直接集成了 Spring 中用于整合 AQMP 协议的 AmqpOutboundEndpoint，该类来自于 Spring Integration 框架，并提供了如下所示的 send 方法进行消息的发送：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43887738261924730000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void send(String exchangeName, String routingKey, final Message&lt;?&gt; requestMessage, CorrelationData correlationData) {
   if (this.amqpTemplate instanceof RabbitTemplate) {
      // 实现消息格式的转换
      MessageConverter converter = ((RabbitTemplate) this.amqpTemplate).getMessageConverter();
      org.springframework.amqp.core.Message amqpMessage = MappingUtils.mapMessage(...);
      // ...
      // 实现消息发送
      ((RabbitTemplate) this.amqpTemplate).send(exchangeName, routingKey, amqpMessage, correlationData);
   }
   else {
      // 实现消息转换和发送
      this.amqpTemplate.convertAndSend(exchangeName, routingKey, requestMessage.getPayload(), message -&gt; {
         getHeaderMapper().fromHeadersToRequest(requestMessage.getHeaders(), message.getMessageProperties());
         return message;
      });
   }
}`, `43887738261924730000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; requestMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CorrelationData&lt;/span&gt; correlationData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amqpTemplate &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RabbitTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 实现消息格式的转换&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;MessageConverter&lt;/span&gt; converter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RabbitTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amqpTemplate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessageConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amqp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;core&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt; amqpMessage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MappingUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 实现消息发送&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RabbitTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amqpTemplate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amqpMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; correlationData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 实现消息转换和发送&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amqpTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;convertAndSend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; requestMessage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPayload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;getHeaderMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromHeadersToRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestMessage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这里依赖于 Spring Amqp 提供的 AmqpTemplate 接口实现消息发送，而 RabbitTemplate 是 AmqpTemplate 的一个实现类。同时，我们还注意到这里通过 MessageConverter 工具类完成了从 org.springframework.messaging.Message 到 org.springframework.amqp.core.Message 这两个消息数据结构之间的转换。&lt;/p&gt;
&lt;p&gt;介绍完消息发送，接下来我们来看消息的消费。&lt;/p&gt;
&lt;p&gt;RabbitMessageChannelBinder 中与消息消费相关的是 createConsumerEndpoint 方法。类似的，这个方法中也大量使用了 Spring Amqp 和 Spring Integration 中的工具类。该方法最终返回的是一个 AmqpInboundChannelAdapter 对象。在 Spring Integration 中，AmqpInboundChannelAdapter 是一种 InboundChannelAdapter，代表面向输入的通道适配器，提供了消息监听功能，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;63118399124551510000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected class Listener implements ChannelAwareMessageListener, RetryListener {
   @Override
   public void onMessage(final Message message, final Channel channel) throws Exception {
      // 省略相关实现
   }
}`, `63118399124551510000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Listener&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChannelAwareMessageListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RetryListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 省略相关实现&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在这个 onMessage 方法内部，最关键的是用于创建消息的 createMessage 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78540583345552340000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private org.springframework.messaging.Message&lt;Object&gt; createMessage(Message message, Channel channel) {
   // 创建消息体
   Object payload = AmqpInboundChannelAdapter.this.messageConverter.fromMessage(message);
   // 创建消息头
   Map&lt;String, Object&gt; headers = AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties());
   // ...
   // 创建消息
   final org.springframework.messaging.Message&lt;Object&gt; messagingMessage = getMessageBuilderFactory()
      .withPayload(payload)
      .copyHeaders(headers)
      .build();
   return messagingMessage;
}`, `78540583345552340000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messaging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Channel&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 创建消息体&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AmqpInboundChannelAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messageConverter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 创建消息头&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AmqpInboundChannelAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headerMapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toHeadersFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 创建消息&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messaging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; messagingMessage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMessageBuilderFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withPayload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;copyHeaders&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; messagingMessage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，在这个 createMessage 方法中，我们完成了消息数据格式从 org.springframework.amqp.core.Message 到 org.springframework.messaging.Message 的反向转换。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-18&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;针对 Spring 框架，关于消息通信机制也是一个非常好的面试考查点，因为 Spring 框架对消息发送和接收过程进行了抽象，这种抽象很有参考价值。虽然这类面试题中规中矩，但回答起来也有一定的难度，原因在于在 Spring 框架中，关于消息通信机制的实现组件并不是只有一个，所有想要完整阐述其对消息发送和接收的抽象过程需要比较广的知识体系。&lt;/p&gt;
&lt;p&gt;可以说针对类似这样的出题方式，要求面试者对主流开源框架有较高的理解程度。在 Spring 框架中，关于消息通信的实现组件和框架主要有三个，即位于 Spring 内核中的 Spring Messaging 组件，以及独立的 Spring Integration 和 Spring Cloud Stream 框架。&lt;/p&gt;
&lt;p&gt;其中，Spring Messaging 是 Spring 家族中处理消息通信的底层框架。而 Spring Integration 在定位上属于一种企业服务总线，依赖于 Spring Messaging。Spring Cloud Stream 是 Spring Integration 的一种增强，同时与 Spring Boot 体系进行了融合。&lt;/p&gt;
&lt;p&gt;从设计抽象角度讲，Spring Messaging 组件提供了用于消息通信最基本的消息（Message）、消息通道（MessageChannel）等概念的定义，Spring Integration 框架则在此基础上给出了一套完整的 ESB 解决方案。而本讲重点阐述的 Spring Cloud Stream 框架在定位上则是一种平台，完成了与各个消息中间件的无缝集成。&lt;/p&gt;
&lt;p&gt;关于如何构建一个统一化的消息通信平台，Spring Cloud Stream 是我们值得深入分析和研究的一个框架。对于消息通信而言，我们需要分别实现消息的发布者和消费者。在 Spring Cloud Stream 中分别是 Source 和 Sink 组件。而消息的传递显然应该用到通道，所以 Spring Cloud Stream 也包含了 Channel 组件。最后，作为 Spring Cloud Stream 框架在设计上的一大特色，Binder 组件专门用于屏蔽与各种消息中间件之间的技术差异，为开发者提供统一的 API。&lt;/p&gt;
&lt;p&gt;这样，我们就把 Spring Cloud Stream 中的四个核心组件都梳理了一遍，分别是 Binder、Channel、Source 和 Sink。在回答这类问题时，可以围绕 Spring Cloud Stream 的基本架构把这些组件都介绍到，并重点对 Binder 组件做细化阐述。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-16&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲讨论了分布式系统中与消息通信相关的话题，但我们的切入点并不是消息通信本身，而是关注如何构建一个跨消息中间件的统一消息通信平台。与前面几讲的行文思路不同，我们在引入 Spring Cloud Stream 时并不是直接介绍它的实现原理，而是从 Spring 家族中所具备的各种消息通信组件和框架开始，引出 Spring Cloud Stream 的基本架构。然后，作为一款平台型的开源框架，我们重点对 Spring Cloud Stream 如何与各个消息中间件之间实现整合的过程做了源码级的深入分析。&lt;/p&gt;
&lt;p&gt;从下一讲开始，我们将进入到一个新的模块的讲解，这个模块包含了一组通用型的技术组件。我们要引入的第一个通用型技术组件是动态代理。那么，动态代理在分布式服务中起到什么作用？这就是下一讲要展开的内容。&lt;/p&gt;
&lt;h1 id=&quot;动态代理：动态代理在分布式服务中起到什么作用？&quot;&gt;&lt;a href=&quot;#%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%EF%BC%9A%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E5%9C%A8%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E4%B8%AD%E8%B5%B7%E5%88%B0%E4%BB%80%E4%B9%88%E4%BD%9C%E7%94%A8%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;动态代理：动态代理在分布式服务中起到什么作用？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们介绍了消息通信这个在分布式系统构建过程中非常常用的技术组件，这也是我们针对分布式系统讨论的最后一个专用技术组件。&lt;/p&gt;
&lt;p&gt;从本讲开始，我们将进入到另一类技术组件的讲解，这类技术组件并不局限于只能用于分布式系统的构建过程，而是具有更大的通用性和灵活性。首先，我们要引入的是动态代理机制。&lt;/p&gt;
&lt;p&gt;在系统设计过程中，对象之间相互依赖会造成耦合度过高，我们需要引入一个中间类来消除或缓解在直接访问目标对象时所带来的问题。但是对于发起访问的对象而言，通常希望这个中间类的存在是无感知的，这时候我们就可以引入动态代理机制。&lt;/p&gt;
&lt;p&gt;那么，问题就来了，作为一种通用型的技术组件，动态代理在分布式系统构建过程中起到什么作用呢？本讲内容将和你一起探讨这个话题。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-16&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-16&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在系统设计领域，代理（Proxy）是一种常见的技术组件，主要目的就是在访问目标对象之前添加一层代理对象，用于消除或缓解在直接访问目标对象时带来的问题。&lt;/p&gt;
&lt;p&gt;而在现实应用中，代理机制也非常常见，可以说处处是代理。举一个简单的场景，假设我们需要在服务层组件中调用数据访问层组件，并记录一个操作日志。通常，服务层组件有很多方法，而对所有方法操作都需要添加日志。显然，在每个方法中手工调用同一个日志方法不是一种很好的解决方案，会造成代码冗余，增加维护成本。这个时候，代理机制就可以派上用场了。&lt;/p&gt;
&lt;p&gt;我们可以构建一个代理对象，然后由这个代理对象统一实现日志记录操作，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-45-02-af2cc31482492df573c88973b5ef5ab4-6dd13.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 459px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 20.697167755991284%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsSAAALEgHS3X78AAAA/UlEQVQY0x2Q/U6DMBTFef9H0BhdTIx/kAVtUJMhCjLCIJuwMLpZSjflQ1kwcS9wvPQmTW/7u+fc2xplWaBpSloCquI4nf4wRtd9YbmcIksZFpEJXjxik9tQSmg+1inF0bYSdS20x+EgYPjeNbyXC8yDCZL4hqDSAs4zKMnwuX9C4F9RbuPYu3S/1jzPVwhJ4zrnePMu4T6fYZ2x0XCCvWI0HaNpbiHlVguyLEaRm+gaB7+Dh6518LG7043GCMNXbLkJsbNQinvI0sL7agojTX0yS1DJBMVmjmE4akHf11T4QE0s4jPiMzKw6QWV5t8/LZ0DYjGZRbQv6Fsi/ANYxhgInYNHxAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 45 02&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-45-02-af2cc31482492df573c88973b5ef5ab4-6dd13.png&quot; data-srcset=&quot;/static/2024-11-27-11-45-02-af2cc31482492df573c88973b5ef5ab4-fad05.png 200w,
/static/2024-11-27-11-45-02-af2cc31482492df573c88973b5ef5ab4-a671c.png 400w,
/static/2024-11-27-11-45-02-af2cc31482492df573c88973b5ef5ab4-6dd13.png 459w&quot; data-sizes=&quot;(max-width: 459px) 100vw, 459px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;JDK 自带的动态代理机制组成结构是怎么样的？&lt;/li&gt;
&lt;li&gt;Dubbo 中的远程调用本地化机制背后使用的是什么技术？&lt;/li&gt;
&lt;li&gt;为什么 MyBatis 中的 Mapper 接口没有实现类但却能提供 SQL 执行功能？&lt;/li&gt;
&lt;li&gt;如果让你实现类似动态代理的执行效果，你有什么思路？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事实上，代理机制背后的应用场景以及实现原理是面试过程中非常高频的一类面试题，可以考查的范围非常广，对候选人的要求也很高。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-17&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;在分布式系统构建过程中，动态代理是一种非常通用的实现机制，被广泛应用于 Dubbo 和 MyBatis 等各种开源框架中。面试过程中，面试官也通常会基于这些框架来考查候选人对这一技术组件的掌握程度。&lt;/p&gt;
&lt;p&gt;那么，我们应该如何掌握与代理机制相关的解题思路呢？这里我总结了三点思路。&lt;/p&gt;
&lt;p&gt;首先，就理论知识而言，我们需要明确代理机制的概念和分类。关于代理机制的概念，你可以结合设计模式中的代理模式一起进行理解。而关于代理机制的分类，一般认为具体的表现形式有两种，一种是静态代理机制，一种是动态代理机制。面试过程中，关于静态代理机制只需要简单介绍即可，重点要做展开的是动态代理机制。&lt;/p&gt;
&lt;p&gt;然后，我们需要对动态代理机制的主流实现技术展开讨论。在 Java 的世界中，想要实现动态代理，主要有三种实现方式，即 JDK 自带的代理类以及第三方的 Cglib 和 Javassist。这些实现技术的使用方式大致相同，但背后的原理却各有特点。候选人至少需要对其中的一种实现方式有深入的理解，建议可以从 JDK 自带的代理类进行切入。&lt;/p&gt;
&lt;p&gt;最后，明确了概念和技术之后，接下来就是具体的应用场景了。可以说，动态代理机制在各个主流的开源框架中应用非常广泛。因为它是一种通用型的技术组件，所以可以根据不同的应用场景解决不同的问题。这里也需要候选人对主流开源框架中涉及到动态代理的常见应用场景和实现方式有足够的了解。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-19&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;通过问题背景部分所介绍的应用场景，实际上我们可以梳理代理机制中存在的三种不同的角色，即抽象角色、代理角色和真实角色，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-45-56-cb4bb2c78dfef57e1d08bcfcceff8759-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.386996904024766%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABF0lEQVQY01WQi2rCQBBF8/9fI6aCJjHaWgpFsL4aNWvM07R52U1sVBLkdrKh0C4c5s7sshxGwp9T1zWG5hidtYzutoddaMGxZtgYMpaLlrdZB+vVA9HDailjMe+Kam778L0xpIo+qe933KoKZVnCCz0EUYAwCXG5XXC9foPzBHmeIucpvk5xm4lmznkqKPIM5yKDpDMG2TCgULUcB6/BFKqlE0NYkYOjv4Zt6zDeFTBTh+c+k7GCPRtRnogZM+ntfoQknkI6FQWiLBNwzuEeXWHY0BiW5RkJ2SbJB+I4RJp+tpX639zcNWRZBOn/DiuMDxP0DxoGzlDs0LPnYGyA7WaAwH8S7JkGc6eQlUaWj9SrZKkiil7wAycNac3ribdCAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 45 56&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-45-56-cb4bb2c78dfef57e1d08bcfcceff8759-0e173.png&quot; data-srcset=&quot;/static/2024-11-27-11-45-56-cb4bb2c78dfef57e1d08bcfcceff8759-2fb9f.png 200w,
/static/2024-11-27-11-45-56-cb4bb2c78dfef57e1d08bcfcceff8759-f1e72.png 400w,
/static/2024-11-27-11-45-56-cb4bb2c78dfef57e1d08bcfcceff8759-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，代理角色和真实角色一样实现了抽象角色，但是它是真实角色的一种代理。通过代理角色，开发人员可以在真实角色的基础上添加各种定制化的处理逻辑。&lt;/p&gt;
&lt;p&gt;上图展示代理机制中的三种角色及其对应的职责，转化为面向对象领域的表现形式，就是如下图所示的类层结构：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-46-27-4d0bde33b798b76b0df8df86566cfea9-a3fce.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 515px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 75.33980582524272%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAACA0lEQVQ4y5VTa3PSUBDN//8DOqNOx34Qx7ZCH0gYTB+8TEigIuRBIaEJBdpSYNKGIAQ4bkIdAaHizpzcnZvdc/fJYEVms1lwjkYeJIlDIR+GJEbAfztATtgnPY7hcLRkuyjMJkLHGaJaTaKuH8EyWSIK4UoLw9DPYdvO/xO67gg8f4SGeQKz/iXAjRVFjg/j6cndjtA3+G3keRPIZRGqmkK5dIESQVGSdPJUjvFf9hsjXPfydApMJi/b/DPCbvcBhlFFq9VAsZjHZUEMdP+u07nfLsJFA0XJUgOOoddYKPIhIYK6Ecd1PQpZTm5PSN8gRV0X4Q4y1OUYSj8+Ew7QbnIY/eTpER5znhcI/7w2C4w1jcejTWNjsJTuJ0i5EDr3SQycDEWfWvJZBDOlcJYxr/5l4Qxy6QOujZNgFuc4prsQRJF9btYEq/5ru2zbj0inz6AqAq4qeVQ0MYCva6qAVIpDr9db32VdV6l7eoCGVYFp6jS4DgaDATaJ6w6D/82mBcvSyLdGeo0mQAVzcf6epn8XifgrOncg0Hb4NfTT8bwxwVvB+DlVQBBYZDNvwSVeI5t+R/oumJywh9sWB6NGq9VgkZdi6PdtmsNukNY69Ps9tNt34L7u02rGqM4s7tqnKBUjYNjYG5q3KNWHCl7eQzp1iG0lJyRQ/P4RVfL1OU65HfwCetVmzaC2AHMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 46 27&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-46-27-4d0bde33b798b76b0df8df86566cfea9-a3fce.png&quot; data-srcset=&quot;/static/2024-11-27-11-46-27-4d0bde33b798b76b0df8df86566cfea9-96d1c.png 200w,
/static/2024-11-27-11-46-27-4d0bde33b798b76b0df8df86566cfea9-2b9d0.png 400w,
/static/2024-11-27-11-46-27-4d0bde33b798b76b0df8df86566cfea9-a3fce.png 515w&quot; data-sizes=&quot;(max-width: 515px) 100vw, 515px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中，代理接口扮演的就是抽象角色，而代理类和委托类则分别充当了代理角色和真实角色，请注意它们都实现了代理接口。&lt;/p&gt;
&lt;p&gt;前面已经提到，代理机制在具体实现上一般有两种方式，一种是静态代理机制，一种是动态代理机制。在 Dubbo、MyBatis 等常见开源框架中，这两种实现方式都有应用。本讲内容重点对动态代理展开讨论，主要因为动态代理理解和实现起来相对比较复杂，而且在 Dubbo、MyBatis 等框架中的应用方式和实现过程也值得我们学习和模仿。&lt;/p&gt;
&lt;p&gt;在接下来的内容中，我们就以 JDK 自带的代理类为例给出具体的实现方式。&lt;/p&gt;
&lt;p&gt;现在假设存在一个 Account 接口，然后需要在调用其 open 方法的前后记录日志。显然，通过静态代理完全能做到这一点，而使用 JDK 自带的动态代理也并不复杂。在 JDK 自带的动态代理中存在一个 InvocationHandler 接口，我们首先要做的就是提供该接口的一个实现类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;47162351664069214000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class AccountHandler implements InvocationHandler {
   private Object obj;

   public AccountHandler(Object obj) {
      super();
      this.obj = obj;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
      Object result = null;
      doBefore();
      result = method.invoke(obj, arg);
      doAfter();
      return result;
   }

   public void doBefore() {
      System.out.println(&amp;quot;开户前&amp;quot;);
   }

   public void doAfter() {
      System.out.println(&amp;quot;开户后&amp;quot;);
   }
}`, `47162351664069214000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccountHandler&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccountHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; arg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;doBefore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;doAfter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doBefore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;开户前&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doAfter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;开户后&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到 InvocationHandler 中包含一个 invoke 方法，我们必须实现这一方法。在这一方法中，我们通常需要调用 method.invoke 方法执行原有对象的代码逻辑，然后可以在该方法前后添加相应的代理实现。在上述代码中，我们只是简单打印了日志。&lt;/p&gt;
&lt;p&gt;然后，我们编写测试类来应用上述 AccountHandler 类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19748537613007190000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class AccountTest {
   public static void main(String[] args) {
      Account account = new RealAccount(&amp;quot;tianyalan&amp;quot;);
      InvocationHandler handler = new AccountHandler(account);

      Account proxy = (Account)Proxy.newProxyInstance(account.getClass().getClassLoader(), account.getClass().getInterfaces(), handler);
      proxy.open();
   }
}`, `19748537613007190000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccountTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RealAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tianyalan&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;InvocationHandler&lt;/span&gt; handler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AccountHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt; proxy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newProxyInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInterfaces&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      proxy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Proxy.newProxyInstance 方法的作用就是生成代理类，当该方法被调用时，RealAccount 类的实例被传入。然后执行到代理类的 open 方法时，AccountHandler 中的 invoke 方法就会被执行，从而触发代理逻辑。这里的类层结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-47-29-64ca411eeda0b3150d52fefc82b4533b-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 52.012383900928796%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABlElEQVQoz41S2U7CUBC9//8Fvii8aIAXMT6ZmADKUnaRsLZAy1ZKC2WJINBynN6CUojGJid37syZ05m5w/DHt9/b/FTkEmqVMFLJABLxO1QrDxCbcdg8vCfenp8OmHP5DbZ9FCygXg2i/B5CqRhApRyAJEY8gkewU/XToGu7gv1+C62WgE47jWYjga6So3sJDu08n7mtHVs8b9lrf6wszOefh8ouuY6fSWKG/1ESU3TmsVwuCHNIkgC5k8ViYWKz2dDM0pDlDPkyaFO1s5mB9XpN/gxaxFXkHAb9Itjrix/ZtJ8GfoNC7hbaSEW324FAdyHpx3CgwDRNxCLXSAvkS/kQi14RR8R4rJPtI9415ftoxkGwkSpjYvQ4NE3h1Wy3W7JlSlB4xbvdDpPJkIQdqDCMPnFcnjrsUKxHvh7nM/zjc1objYaYTnUSNKCPNei6DsuyLrjMeUkvji9tf6/NarWmGUXRrIdpB+9pVs8kqPKYbVuefPazJnxRDi92uj6uYL32hNJbCPlsAI3aI7WpHQRtD/8LuzvynNNQjcMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 47 29&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-47-29-64ca411eeda0b3150d52fefc82b4533b-0e173.png&quot; data-srcset=&quot;/static/2024-11-27-11-47-29-64ca411eeda0b3150d52fefc82b4533b-2fb9f.png 200w,
/static/2024-11-27-11-47-29-64ca411eeda0b3150d52fefc82b4533b-f1e72.png 400w,
/static/2024-11-27-11-47-29-64ca411eeda0b3150d52fefc82b4533b-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;仔细分析上述代码结构，可以发现其遵循设计并实现业务接口 -&gt; 实现 Handler -&gt; 创建代理类这一实现流程，然后在 Handler 中构建具体的代理逻辑。&lt;/p&gt;
&lt;p&gt;上述流程展示了基本的代理机制实现过程。我们可以联想一下很多基于 AOP 机制的拦截器实际上就是类似的流程。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-17&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在本讲的源码解析部分，我们将分别基于 Dubbo 框架和 MyBatis 框架来深入分析在主流开源框架中动态代理机制的应用场景和实现原理。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-远程访问中的代理机制&quot;&gt;&lt;a href=&quot;#dubbo-%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AE%E4%B8%AD%E7%9A%84%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 远程访问中的代理机制&lt;/h3&gt;
&lt;p&gt;当使用 Dubbo 进行远程服务调用时，我们所做的事情就是在配置文件或代码中添加对某个服务的引用，整个过程让人感觉并没有执行任何与远程方法调用相关的网络连接、数据传输、序列化等操作，这就是所谓的远程调用本地化。远程调用本地化得以实现的背后用到的实际上就是动态代理机制。&lt;/p&gt;
&lt;p&gt;在 Dubbo 中，我们知道执行远程调用的是 Invoker 对象。因此，当该对象被创建出来之后，我们就需要为它生成对应的代理对象，完成这一操作的是 ProxyFactory 工厂类，该工厂类的 getProxy 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;48570785022753690000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ProxyFactory {
   @Adaptive({Constants.PROXY_KEY})
   &lt;T&gt; T getProxy(Invoker&lt;T&gt; invoker) throws RpcException;

   @Adaptive({Constants.PROXY_KEY})
   &lt;T&gt; Invoker&lt;T&gt; getInvoker(T proxy, Class&lt;T&gt; type, URL url) throws RpcException;
}`, `48570785022753690000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProxyFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROXY_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROXY_KEY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInvoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Dubbo 中，ProxyFactory 的直接实现类是 AbstractProxyFactory。该类是一个抽象类，除了为每个服务自动添加回声（Echo）功能之外，还预留了一个 getProxy 抽象方法供子类进行实现。&lt;/p&gt;
&lt;p&gt;在 Dubbo 中存在两个 ProxyFactory 的实现类，即 JavassistProxyFactory 和 JdkProxyFactory。其中的 JdkProxyFactory 的实现比较典型，接下来我们就对 JdkProxyFactory 进行展开，该类的 getProxy 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54983719880225520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; T getProxy(Invoker&lt;T&gt; invoker, Class&lt;?&gt;[] interfaces) {
   return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
}`, `54983719880225520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; interfaces&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newProxyInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentThread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContextClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; interfaces&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvokerInvocationHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里看到了熟悉的 Proxy.newProxyInstance 方法，这是典型的 JDK 动态代理的用法。根据传入的接口获得动态代理类，当调用这些接口的方法时都会转而调用 InvokerInvocationHandler。基于 JDK 动态代理的实现机制，可以想象 InvokerInvocationHandler 类必定实现了 InvocationHandler 接口，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;80186632181173450000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class InvokerInvocationHandler implements InvocationHandler {
   private final Invoker&lt;?&gt; invoker;

   public InvokerInvocationHandler(Invoker&lt;?&gt; handler) {
      this.invoker = handler;
   }

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      Class&lt;?&gt;[] parameterTypes = method.getParameterTypes();

      if (method.getDeclaringClass() == Object.class) {
         return method.invoke(invoker, args);
      }

      if (&amp;quot;toString&amp;quot;.equals(methodName) &amp;&amp; parameterTypes.length == 0) {
         return invoker.toString();
      }

      if (&amp;quot;hashCode&amp;quot;.equals(methodName) &amp;&amp; parameterTypes.length == 0) {
         return invoker.hashCode();
      }

      if (&amp;quot;equals&amp;quot;.equals(methodName) &amp;&amp; parameterTypes.length == 1) {
         return invoker.equals(args[0]);
      }

      return invoker.invoke(new RpcInvocation(method, args)).recreate();
   }
}`, `80186632181173450000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvokerInvocationHandler&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvokerInvocationHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invoker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; parameterTypes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameterTypes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclaringClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;toString&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; parameterTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hashCode&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; parameterTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;equals&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; parameterTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcInvocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;recreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里只是把方法的执行转向了 invoker.invoke 方法。关于 Invoker 的介绍不是本讲内容的重点，我们已经在前面介绍服务调用时对其进行了详细的展开，你可以做一些回顾。&lt;/p&gt;
&lt;h3 id=&quot;mybatis-数据访问中的代理机制&quot;&gt;&lt;a href=&quot;#mybatis-%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E4%B8%AD%E7%9A%84%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MyBatis 数据访问中的代理机制&lt;/h3&gt;
&lt;p&gt;在 MyBatis 中，应用动态代理的场景实际上非常多，我们无意对所有的场景都一一展开。这里列举一个最典型的应用场景，即 Mapper 层的动态代理，用于根据 Mapper 层接口获取 SQL 执行结果。&lt;/p&gt;
&lt;p&gt;在开始介绍 MyBatis 中的代理机制之前，我们先来回顾一下 MyBatis 的执行主流程，如下图所示。可以看到 MyBatis 是通过 MapperProxy 动态代理 Mapper。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-49-05-a2ecbf899f55bde2a80e6827c6e8b989-7278c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 651px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 44.70046082949309%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABK0lEQVQoz3VR2VLDMAzM//8QdBh4YPiFJmleU0qdwzmcy7mEVmDGtKAZj631erWSgyh8oTg68HqmdV1J6w86xU8Uh490eT+Si2WxBG50fKAkeeV8oeT0xtiBQsbO50h4wbat1PeG6lpTURRUVZqu1wsLF1SWBeV5zlgld1mmBLd2ImOMvBsGQ9u20DxbKRJAdRwn8sNay+SBiRsTZ3GOhXPTNPKw73vhKZVRmqZcvBQs2PedqwwsOgpxmibJ4QwYSBDRWkueKSVuu64TDnCcXYjDuq65neyHhLPih74z5xT3cOZwFIQRBMyJQ1T2AxiIfwXmiXt/PFh3gpiXI6Iy2nYkh2Nv2/YXhhGBfyfoV0UL/wneciHm59+//OXQhftdJ+LvKHYriJax4+4TcsC6ITqD7VgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 49 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-49-05-a2ecbf899f55bde2a80e6827c6e8b989-7278c.png&quot; data-srcset=&quot;/static/2024-11-27-11-49-05-a2ecbf899f55bde2a80e6827c6e8b989-5503e.png 200w,
/static/2024-11-27-11-49-05-a2ecbf899f55bde2a80e6827c6e8b989-992a5.png 400w,
/static/2024-11-27-11-49-05-a2ecbf899f55bde2a80e6827c6e8b989-7278c.png 651w&quot; data-sizes=&quot;(max-width: 651px) 100vw, 651px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;使用过 MyBatis 的同学应该都接触过这样一种操作，我们只需要定义 Mapper 层的接口而不需要对其进行具体的实现，该接口却能够正常调用并完成 SQL 执行等一系列操作，听起来很神奇，这是怎么做到的呢？让我们梳理一下整个调用流程。&lt;/p&gt;
&lt;p&gt;在使用 MyBatis 时，业务层代码中调用各种 Mapper 的一般做法是通过 SqlSession 这个外观类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;90973178646217600000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`TestMapper testMapper = sqlSession.getMapper(TestMapper.class);`, `90973178646217600000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TestMapper&lt;/span&gt; testMapper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sqlSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TestMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;作为外观类，DefaultSqlSession 把这一操作转移给了 Configuration 对象，该对象中的 getMapper 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82869347389127260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public &lt;T&gt; T getMapper(Class&lt;T&gt; type) {
   return configuration.getMapper(type, this);
}`, `82869347389127260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;让我们来到这里出现的 MapperRegistry 类，会发现真正负责创建 Mapper 实例对象的是 MapperProxyFactory 类。请注意，mapperProxyFactory.newInstance 方法的传入参数是一个 SqlSession，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;8390904511260588000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public T newInstance(SqlSession sqlSession) {
   final MapperProxy&lt;T&gt; mapperProxy = new MapperProxy&lt;&gt;(sqlSession, mapperInterface, methodCache);
   return newInstance(mapperProxy);
}`, `8390904511260588000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SqlSession&lt;/span&gt; sqlSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapperProxy&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; mapperProxy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapperProxy&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sqlSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mapperInterface&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; methodCache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapperProxy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里引出了另一个核心类 MapperProxy，从命名上看，我们可以猜想该类就是一个代理类，因此势必使用了前面介绍的某种动态代理技术。可以先看一下 MapperProxy 类的签名，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;52407786349819280000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class MapperProxy&lt;T&gt; implements InvocationHandler, Serializable`, `52407786349819280000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapperProxy&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Serializable&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，这里用到的是 JDK 自带的基于 InvocationHandler 的动态代理实现方案，因此，在 MapperProxy 类中同样肯定存在一个 invoke 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58397649090465055000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   try {
      if (Object.class.equals(method.getDeclaringClass())) {
         return method.invoke(this, args);
      } else if (method.isDefault()) {
         return invokeDefaultMethod(proxy, method, args);
      }
   } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
   }

   final MapperMethod mapperMethod = cachedMapperMethod(method);
   return mapperMethod.execute(sqlSession, args);
}`, `58397649090465055000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclaringClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invokeDefaultMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExceptionUtil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrapThrowable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapperMethod&lt;/span&gt; mapperMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cachedMapperMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; mapperMethod&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sqlSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于执行 SQL 语句的方法而言，MapperProxy 会把这部分工作交给 MapperMethod 处理。而在 MapperMethod 的 execute 方法中，我们传入了 SqlSession 以及相关的参数。在这个 execute 方法内部，根据 SQL 命令的不同类型（insert、update、delete、select）分别调用 SqlSession 的不同方法。&lt;/p&gt;
&lt;p&gt;目前为止，我们看到了 MapperProxy 类实现 InvocationHandler 接口，但还没有看到 Proxy.newProxyInstance 方法的调用，该方法实际上也同样位于 MapperProxyFactory 类中，该类还存在一个 newInstance 重载方法，通过传入 mapperProxy 的代理对象最终完成代理方法的执行，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35755225336333353000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected T newInstance(MapperProxy&lt;T&gt; mapperProxy) {
   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}`, `35755225336333353000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MapperProxy&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; mapperProxy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newProxyInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapperInterface&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mapperInterface &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mapperProxy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;作为总结，我们梳理了 MyBatis 中 Mapper 层动态代理相关类的类层结构，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-11-27-11-50-25-880923778297c19ddab3568afe4a244f-21746.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 449px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 71.0467706013363%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACG0lEQVQ4y5VTaXPSUBTN//+uM1orWupS2Wrp6FQoECLSsATCltoSFkMCpaWgRZAtx0MQ+sVRmpk7773cd7dzzhOwxWfbC2ftdCxkMwHo1RAyaT/NB4XnghpEox5GOhWE8JCE3SsTphHCr9EZykUf1JwHmdRrfNXeYzRMwjKlhyW8YkIx9txJIsVdSEgvnDUS3kE8touccgjBtu2tE7bbLY53iOuuhLYVY0cRWhSdtojejQQly5EXiwX+Z+ui0+kULeMbms0GzJZBzGSo+RxM00CzUWdBa7uRV13aW90TUqkwVDWMSlkkBiEUCxGOFXHWcimKYlFEv9/fBPRvb/AlEUSJvmVcSv4ATRORzYZwd/cDghTfR/T0CWLRHcjJPeJAwMVdpGU3Umd7qJQClETNGX0ymaFcVnAafoyTj4/oX93JZV9RPm8xGNxC+HTiIu1+SuCAwT6UCp4/5uXZy27cMIjb+tO0Aos/hVbxo5B/xyl8nM5Lpp+h17uGYBg6waw5Zllcrdrm3G7XoesVVh5sEk6nExJzSaab9OuM0dHtkiRTd3xbkTKbzTAejzEajTAc/tz87/W+E7eRs5/Pl8RhJZulzuzF323pv+9uDlmOEgaPo7m4+IZ47pOQIAk9QqNxsRL2v2wtmfW+Ws3wPbuR+OxCnrgnEy+pxQNcXgToK2779O4TKopEIgIkJUgyjnCuHeO8coy84iVhKn4DPLYKL7EB5aYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 11 27 11 50 25&quot; title=&quot;&quot; data-src=&quot;/static/2024-11-27-11-50-25-880923778297c19ddab3568afe4a244f-21746.png&quot; data-srcset=&quot;/static/2024-11-27-11-50-25-880923778297c19ddab3568afe4a244f-17e1d.png 200w,
/static/2024-11-27-11-50-25-880923778297c19ddab3568afe4a244f-f4f9f.png 400w,
/static/2024-11-27-11-50-25-880923778297c19ddab3568afe4a244f-21746.png 449w&quot; data-sizes=&quot;(max-width: 449px) 100vw, 449px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;总体而言，基于代理机制，MyBatis 中 Mapper 层的接口调用过程还是比较简单明确的，这里采用的实现方案也比较经典，相关的类层结构设计也可以用作日常开发工作的参考。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-19&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于动态代理的实现技术是面试过程中的一个要点。动态代理是代理机制的主要实现方式，相比静态代理更为复杂也更为实用。常见的动态代理实现技术包括 JDK 自带的代理类、第三方的 Cglib 和 javassist。在回答该题时，这三个名词是一定要点到的，至于具体的细节，视面试的进展可以合理进行展开，包括给出一些自己开发过程中的实践体会，或者部分核心类的介绍。针对这道题而言，我们关注的是动态代理的技术本身，而不是应用的方式和场景。在准备这类面试题时，我们可以事先对动态代理的三种实现方式做一些示例代码，从而确保自己先有一个感性的认识。&lt;/p&gt;
&lt;p&gt;针对具体框架的应用场景和实现机制的考查，是与动态代理机制相关的另一个面试要点，我也经常拿这些问题来面试不同层级的候选人。例如，对 Dubbo 而言，存在两种动态代理的实现机制，即 JDK 和 Javassist。尽管默认采用的是后者，但由于 Javassist 的实现过程过于复杂，一般不大适合作为一个面试题进行考查，所以更多的时候我们还是会拿 JDK 中的动态代理机制来进行讨论。&lt;/p&gt;
&lt;p&gt;在回答这种问题上，第一步肯定是回答关于动态代理实现机制本身的内容，例如这里我们就需要对 JDK 中的 InvocationHandler 接口和 Proxy 静态类做详细的介绍。然后，针对 Dubbo 框架，我们也要明确所有的远程调用过程都是封装在一个个的 Invoker 对象中，所以动态代理的目的实际上就是把本地代码的调用过程转移到对 Invoker 对象的使用上，这是我们在回答类似问题上的基本思路。&lt;/p&gt;
&lt;p&gt;Dubbo 中动态代理的整个执行流程还是比较清晰和明确的，在理解上建议关注于各个接口方法的输入输出参数，尤其是 Invoker 对象的使用方式。&lt;/p&gt;
&lt;p&gt;再比如说，类似“在 MyBatis 中，为什么我们只需要定义 Mapper 层的接口而不需要对其进行具体的实现就能完成正常的 SQL 执行过程？”这样的问题也很经典，经典之处在于一般的开发人员很难通过题目中的描述明确问题的考点。&lt;/p&gt;
&lt;p&gt;实际上，该题考查的还是代理机制。而从问题的问法上是基于 Mybatis 这个框架，但问题本身是跟 MyBatis 没有关系的，关注的是“没有具体实现的接口如何完成方法调用”这个本质问题。我们要理理思路来慢慢解开这道题的回答内容。&lt;/p&gt;
&lt;p&gt;首先，我们明确通过 MyBatis 可以提供一系列的自定义 Mapper 接口，我们使用这些接口定义就可以执行数据库的查询、更新等操作。这是框架应用上的具体做法，相信所有用过 MyBatis 的同学都能明确这一点。然后，我们再次明确，通过接口定义就能提供方法调用的基本原理就是对这些接口进行了动态代理。只要明确了这一点，就可以把动态代理的相关内容嫁接到这个问题上，从而完成该问题的回答。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-17&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本章介绍动态代理的基本概念、应用场景、组成结构以及技术实现方式。这些内容在 Dubbo 和 MyBatis 框架中都得到了应用，其中 Dubbo 主要使用动态代理实现远程方法的调用，而 MyBatis 则基于它来完成数据访问。&lt;/p&gt;
&lt;p&gt;介绍完动态代理，下一讲我们要讨论的是缓存机制。在分布式系统构建过程中，缓存也是一个通用型的技术组件，其应用方式也比较多样化。那么，如何在数据访问过程中嵌入缓存机制？我们下一讲再聊。&lt;/p&gt;
&lt;h1 id=&quot;应用缓存：如何在数据访问过程中嵌入缓存机制？&quot;&gt;&lt;a href=&quot;#%E5%BA%94%E7%94%A8%E7%BC%93%E5%AD%98%EF%BC%9A%E5%A6%82%E4%BD%95%E5%9C%A8%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%B5%8C%E5%85%A5%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;应用缓存：如何在数据访问过程中嵌入缓存机制？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们介绍了动态代理这一通用型的技术组件。本讲继续围绕通用型技术组件展开讨论，我们将要介绍的是缓存机制。&lt;/p&gt;
&lt;p&gt;想要理解缓存的设计策略，我们一般都会从应用层缓存的角度切入来具体探讨如何使用缓存的方法。缓存的作用在于减少数据的访问时间和计算时间，表现为将来自持久化或其他外部系统的数据转变为一系列可以直接从内存获取的数据结构的过程。&lt;/p&gt;
&lt;p&gt;那么，针对分布式系统，如何在数据访问过程中嵌入缓存机制？这是开发人员都需要面对的一种场景，也是面试过程中的高频话题。本讲内容将和你一起探讨应用缓存的实现机制和底层原理。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-17&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-17&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&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;Mybatis 中包含哪些具体的缓存结构，各自有什么特点？&lt;/li&gt;
&lt;li&gt;Mybatis 中的缓存数据是如何进行分级管理的？&lt;/li&gt;
&lt;li&gt;Mybatis 的二级缓存与一级缓存有什么关联关系？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事实上，缓存的基本原理都是类似的，都是一种空间换时间的实现方案。但是，在现实中，我们通常会引入多级缓存的技术手段来对缓存数据进行更为合理和有效的管理，这也是我们在学习主流开源框架时要特别注意的技术点。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-18&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;让我们对缓存相关的问题做一些分析和展开。缓存是一项实现技术丰富、表现形式也比较多样的技术组件。在本讲中，我们关注的是应用程序内部的本地缓存实现策略。针对这种场景，最简单的缓存实现策略就是基于 JDK 中的某种数据结构来提供 CRUD 入口。但正如前面所提到的，现实中的缓存实现策略往往没有那么简单，多级缓存的设计思想普遍存在。&lt;/p&gt;
&lt;p&gt;因此，在应对这类面试题时，我们需要从多级缓存的设计思想出发梳理对应的组成结构和实现策略。这是应对这类面试题的第一点思路。&lt;/p&gt;
&lt;p&gt;有了理论基础之后，我们就可以采用一定的数据结构或工具来实现多级缓存。而目前主流的开源框架中也不乏实现多级缓存的经典场景，尤其是那些与数据库访问相关的 ORM（Object Relational Mapping，对象关系映射）框架。以常见的 Mybatis 框架而言，就内置了由一级缓存和二级缓存所构成的一整套多级缓存机制。&lt;/p&gt;
&lt;p&gt;通过对框架源码的学习，我们就能掌握应对上述面试题的回答技巧。同时，这些多级缓存机制的实现原理也为我们应对类似的应用场景提供了很好的参考。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-20&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;关于缓存的设计策略是一个比较大的话题，而缓存的分层思想是其中的一个核心设计思想。本节内容先从经典的缓存分层架构出发，分析应用层缓存的分级管理模式。&lt;/p&gt;
&lt;p&gt;缓存的核心作用在于降低获取目标数据所需的时间，具体表现上，通常是把来自持久化或其他外部系统的数据转变为一系列可以直接从内存获取的数据结构的过程。&lt;/p&gt;
&lt;p&gt;在学习互联网系统中的主流架构时，我们经常会看到类似如下所示的架构图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-21-50-933955649f7b3c7ab129ff9050f3e81d-700d4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 540px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 15.555555555555555%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAyElEQVQI1x2O22rCUBBF8//fYwtqJNTGhgTxySK5G5KTY+7GpK0U6uq0D8PAbNZeY7wfl1QXiyQ2GceZNHEplUngP3G9dugyQhVrsvMSpXyGoZfsmShcyH5lmj4Ig81/RxxbGKXaMt1s5skjDGPS1KatN1y0KQWZQHv6zhJgLbcTvn+iyFdk6YI8d4iiRJ5Z0dQmbeNgqGILjwO30aWqGgEP3L8cuvaFrqvQOuT77opwR10HMpqht/mc3yhLj7btRbTj8fMn9vgFw2TX6KYevokAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 21 50&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-21-50-933955649f7b3c7ab129ff9050f3e81d-700d4.png&quot; data-srcset=&quot;/static/2024-12-16-11-21-50-933955649f7b3c7ab129ff9050f3e81d-555fa.png 200w,
/static/2024-12-16-11-21-50-933955649f7b3c7ab129ff9050f3e81d-eb4c6.png 400w,
/static/2024-12-16-11-21-50-933955649f7b3c7ab129ff9050f3e81d-700d4.png 540w&quot; data-sizes=&quot;(max-width: 540px) 100vw, 540px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，在 Nginx、Redis 等组件中都可以存在缓存机制，我们无意对所有缓存机制进行展开，本讲关注的是上图中应用程序层的缓存。这里的应用程序泛指诸如 Tomcat、Undertow 等应用程序容器，也包括像 Spring、Dubbo、Mybatis 等的开源框架，以及我们自己开发的业务系统。&lt;/p&gt;
&lt;p&gt;如果我们分析应用层所具备的缓存实现技术，都可以抽象出通用的缓存结构，下图就是一种常见的缓存表现形式：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-24-15-3356949419823f291ffebd4fd8d3d6dc-72de5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 648px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.28395061728394%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAAByklEQVQozz1SiW7aQBTk/z+jUiu1qqKQtAUaQKUQwDnA1Bc+8H1BYsDBBJDaTMem6kqjXc++N2/e89ZUpYHQ/465dgVNqUOfX/+HKl/C1L8ijW9g21OUa7NZIY5mSFMJafIPPEeRjDzPUXPsEQJ/QMEOLLNH/ICqUGDxk6Id+N4tnlYCE/RK0F7IcJ0rijYRhTQTNFiwWRkKwxC133+A4+mNlQtYlk94UFWTjoIKq9UWxyPwhvNyHY0iLYp0kZSIOlilXRohlyaoeZ7IahM4tsDqI7rq4/GhgYU1YtAQnnvPtqZYpm4lGPgGHXexy4fYbvrEAMXulnFtdkGHhv4Nz08dKNJnipUzq8OY1+EsvjC5yeodFC89JkxRFCfc3fUwEz9iOHjH/ROE8Xs83H/Ar9kl4jhCLcsySJKEyeQRsixBFEWI0ykURa74890E6/Uau90eui6y1Rv4bot7mzNuVd+u00aSsOXlMoTrGpyXBs8zYZoyRQUEgUVuDsfRySnYbjMcDieORoNl1Om+RdcNCpevpMkXcX12aBpNHA8CXvdjZM89BF55eVEF74sRMcbhdQhDH1c/Lgwtxg2QbwXOb1xh9yKQ7yNOYvwFoNw5tjSNIkAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 24 15&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-24-15-3356949419823f291ffebd4fd8d3d6dc-72de5.png&quot; data-srcset=&quot;/static/2024-12-16-11-24-15-3356949419823f291ffebd4fd8d3d6dc-bb436.png 200w,
/static/2024-12-16-11-24-15-3356949419823f291ffebd4fd8d3d6dc-c5634.png 400w,
/static/2024-12-16-11-24-15-3356949419823f291ffebd4fd8d3d6dc-72de5.png 648w&quot; data-sizes=&quot;(max-width: 648px) 100vw, 648px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，数据表示为 Key-Value 对，然后我们对 Key 施加一定的算法获取其哈希值。有了这个哈希值，我们就可以找到目标 Value 所在的位置并获取值。&lt;/p&gt;
&lt;p&gt;各类缓存实现工具，尽管其支持的数据结构以及数据在内存中的分配和查找方式有所不同，但基本结构模型都与上图类似。从该图中，我们也认识到缓存本质上是一种空间换时间的做法。&lt;/p&gt;
&lt;p&gt;现在，我们已经明确了单级缓存的基本结构，让我们对上图进行扩展和延伸，把讨论范围扩大到多级缓存。如果对应用程序层的缓存进行进一步分析，我们发现同样存在分级模式，这种分级模式通常包括两级，即一级缓存和二级缓存，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-25-21-9b070bb9587d50c7eda1cd40ede94a6a-f72ce.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 419px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 54.41527446300716%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB8klEQVQoz22RC1PaUBCF8/9/RqdOq0VGh4ICTi0FFfM05AERFHkbEx4dUAK5yenmtjgFvDNndvaw+faBgH8vjmPsvsTb9Tf5eDyGbdtwXXerVvi/iDGGfr+PVquF5XL5DvF9H51OB/P5/N1bLBYYjUYIw3CrkRDHETaKIobhcMCBq1WQlHF5nod2+3ELGARL3ih5yXcbhrC75nrN8PYW7K3v+5OtPAwZJpPZXp3Q7bahqlewTAlOQ4Msl1Gt/oTjaLAsGXVbgWGIKJXyaNQ1upsCk2pluYKrygWMmgj9rgpNu0G9rkOo3hwTV8FwUEDnKUuANGr6Efq9c/RISXxqZyGLn9Ht5NB+zGA2LYGFEtYrkWsV3NK6CnrdIgSjlgVby5BuDwh6QdBzrlbzO3TtiODf0LBPCXqGQb9IW5zSVCkawkYcaRR1OvMdRZPOUoFQ03N4XVxDUw/5j8FSpAYKPPcS1etPkMQDPLSyCMlLANNJGZrylWotREzlXsQSsAHv5RcETc1QUsO9k6FpUjCNYy7LTNNkJ3w600hDVQ55U1n6Avf5kjdnocphSUwYLy4BVeWEEoev8LEM+N4PapBCw0rj96xMXp37yZp/ZXBvOqGVJalI98rThIUP5TTysK0cTZkl5eifzqN5X9jTQ7NAt83jD6t0Jsm7DtZ7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 25 21&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-25-21-9b070bb9587d50c7eda1cd40ede94a6a-f72ce.png&quot; data-srcset=&quot;/static/2024-12-16-11-25-21-9b070bb9587d50c7eda1cd40ede94a6a-58799.png 200w,
/static/2024-12-16-11-25-21-9b070bb9587d50c7eda1cd40ede94a6a-69db4.png 400w,
/static/2024-12-16-11-25-21-9b070bb9587d50c7eda1cd40ede94a6a-f72ce.png 419w&quot; data-sizes=&quot;(max-width: 419px) 100vw, 419px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;简单来说，常见的一级缓存就是指一次请求（Request）级别或者说会话（Session）级别的缓存。针对第一次查询操作，我们会把从数据库中获取的数据放在会话中。针对后续的查询操作，我们就可以直接从会话中拿到目标数据并直接返回。&lt;/p&gt;
&lt;p&gt;而二级缓存的范围则更大一点，它是一种全局作用域的缓存。只要应用程序处于运行状态，那么所有请求和会话都可以使用。&lt;/p&gt;
&lt;p&gt;多级缓存代表着一种架构设计的方法论，在多款开源框架中都有对应的实现方案。接下来，我们将基于 Mybatis 框架来分析它的一级缓存和二级缓存。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-18&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;mybatis-一级缓存&quot;&gt;&lt;a href=&quot;#mybatis-%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mybatis 一级缓存&lt;/h3&gt;
&lt;p&gt;Mybatis 的一级缓存是二级缓存的基础，我们需要深入理解一组 Cache 对象，以及这些 Cache 对象与 SQL 执行器（Executor）之间的交互关系。&lt;/p&gt;
&lt;p&gt;在 Mybatis 中，存在如下所示的一个 Cache 接口，代表了对缓存操作最高层次抽象。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19953318081914515000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Cache {
   String getId();
   void putObject(Object key, Object value);
   Object getObject(Object key);
   Object removeObject(Object key);
   void clear();
   int getSize();

   default ReadWriteLock getReadWriteLock() {
      return null;
   }
}`, `19953318081914515000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadWriteLock&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getReadWriteLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cache 接口中的方法都是自解释的，而围绕该接口的类层结构如下图所示。在该图中，Cache 接口代表一种抽象，而位于图中央的 PerpetualCache 代表该接口的具体实现类。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-26-55-b1f888782887552495b1e3d67e672257-a2b3c.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 434px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 60.13824884792627%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAACAElEQVQoz32SW2+bQBCF/f//UG8vUR6jxE3U2rCAAXM1sFyMm1a2wadzxnHUvBRphA27335nhsVyM+De7PDle4CHZMSdm+Prc4hvrJcQn5983K0zfFpu0Bz+4H/X5XLBYvh9glOmWK4cPDsenn6s8Sj1YgIUtkeQ5AiyUu4FqqbBfr/HwBoGuQ/yf4BtLY7Ho0IXwIyiL1DkCcJwA89zMcoi1jD06Dsr9w6d3nu0srltW/R9B2sb9PLsfD6rnQKneUbWl8jyFD/XKxjPQxRHSLMM42FEI5to0HYtajHsOsIE3PEQKQHzoNPpdAXOFwF2hQASGGME6OoGLuSGuqnVylqrNgTzHX/zoHEccRLDaZo+AmlIoO8bRFGoxV4x2jWmVQCN2b+bIdf8e2nkXCJvky1WEtlx1mJp4BpXTHox2KOuazQSl3ZVXSno1ks+0+HIOp3yNE8ylBLxNlaYJyBXy9Ee0rCqKlgBNhKbQLaELVCgVFXtdN07kIZpmsiEDcIoUjN+DhzALSYB/GRopj2+RZb4s6T8MOVcDSMEgQ9PKpQpx3EsPXX1ZDae0BusfrO8Rm/1PcGUUMNyX+mUN5sAkURnZM/3BB5I7INu4LRpSWtGJ4CHceLtWxLWuyEhy+WjxPZgfF8N8qLQnjUC2+1KHQ4tOSDC+bySZ6+vvzQ2P/C/uDSDYDyJkQAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 26 55&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-26-55-b1f888782887552495b1e3d67e672257-a2b3c.png&quot; data-srcset=&quot;/static/2024-12-16-11-26-55-b1f888782887552495b1e3d67e672257-6a6e0.png 200w,
/static/2024-12-16-11-26-55-b1f888782887552495b1e3d67e672257-8250c.png 400w,
/static/2024-12-16-11-26-55-b1f888782887552495b1e3d67e672257-a2b3c.png 434w&quot; data-sizes=&quot;(max-width: 434px) 100vw, 434px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图中的多数缓存实现类也都是自解释的，通过名称基本可以判断出它们的功能。而当我们想使用各种缓存类时，可以通过如下所示的装饰方法完成缓存对象的创建：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;91055322344966140000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Cache cache = new XXXCache(new PerpetualCache(&amp;quot;cacheid&amp;quot;))`, `91055322344966140000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XXXCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerpetualCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cacheid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果把这里的 XXXCache 替换成 FifoCache 就代表着这个新创建的 Cache 对象具备了 FIFO 功能。其他缓存类的使用方法也是一样。&lt;/p&gt;
&lt;p&gt;事实上，除了 PerpetualCache 类之外，Mybatis 其他的缓存实现类都是 Cache 的装饰器。而 PerpetualCache 是 Mybatis 中默认使用的缓存类型，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82438994684355690000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class PerpetualCache implements Cache {
   private final String id;
   private Map&lt;Object, Object&gt; cache = new HashMap&lt;&gt;();

   public PerpetualCache(String id) {
      this.id = id;
   }

   // 省略 getter/setter
   // ...
}`, `82438994684355690000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerpetualCache&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerpetualCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 省略 getter/setter&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，整个 PerpetualCache 类的代码结构非常简单，除了一个 id 属性之外，代表缓存的 cache 属性只是一个 HashMap，是一种典型的基于内存的缓存实现方案。这里的几个方法也比较简单，所有对缓存的操作实际上就是对 HashMap 的操作。&lt;/p&gt;
&lt;p&gt;我们知道在 Mybatis 中存在一个配置项，用于指定一级缓存默认开启的级别，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15081122871312935000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;setting name=&amp;quot;localCacheScope&amp;quot; value=&amp;quot;SESSION&amp;quot;/&gt;`, `15081122871312935000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;setting&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;localCacheScope&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;SESSION&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Mybatis 中一级缓存又可以分为两个级别，即 SESSION 级和 STATEMENT 级，它们之间的区别在于缓存范围的大小。对于 STATEMENT 级别而言，缓存的作用范围就是当前 SQL；而如果是 SESSION 级，则查询结果一直会位于该会话中。但是，要注意由于一级缓存是独立存在于每个 Session 内部的，因此，如果我们创建了不同的 Session，那么它们之间会使用不同的缓存。&lt;/p&gt;
&lt;p&gt;例如，完全一样的一个操作，如果在两个不同的 Session 中执行，那就意味着存在两份一样的缓存数据，但分别位于两个 Session 中，彼此之间不会共享，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-28-24-a9a164859c85d83d99eccc12c3de2cbd-72de5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 648px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.27160493827161%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABh0lEQVQoz22SWU/CUBCF+/9/A6/6YGIixqghPoCSKPtWIAJl6QKtFGJpWboezy1LQuIkJ/feme/OzF2kJEkQBAG22y1834cwz/MwNww4zm+6FkySxOkYhoL1yB7SmOtuoOs6RzeNS8IZhgkMY4Hd7ggJ2+18/GdRDOhkPW9/8e33wWUu6VqLyTqYjOus1IY6a7I7GZrahLmQYVkjdhPANAeX2HTaYLyFmVjPT6zZg6q2IX337yG37/Cez6BRu4UyzGI0yCIMKojCElZ2mUltJnuDMnpAR7CFDGqVGzbxhBF5/1BCHFWha6+Q3E0RazvPyjkGH7nxGRvng803U22cGruwsF59YuuRXRWwmOcwZFFNfWG8SK5BtWCZb5BsW8ZyKbOTLiuWoChfWP50eJwyN1R47D7vyGeSLgy9yuupY73uYZyyJbIyj8tkVhuTSQVSFPGiTzpbHB8fSiQSiukQo3ioIIyvWDEXvFD6KPwUOOv4Pc7ro4mvo2kaX9U9ea5ZoYhZoyhM9Qc/aViutrQX4wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 28 24&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-28-24-a9a164859c85d83d99eccc12c3de2cbd-72de5.png&quot; data-srcset=&quot;/static/2024-12-16-11-28-24-a9a164859c85d83d99eccc12c3de2cbd-bb436.png 200w,
/static/2024-12-16-11-28-24-a9a164859c85d83d99eccc12c3de2cbd-c5634.png 400w,
/static/2024-12-16-11-28-24-a9a164859c85d83d99eccc12c3de2cbd-72de5.png 648w&quot; data-sizes=&quot;(max-width: 648px) 100vw, 648px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;介绍完一级缓存的基本概念和组成结构，是时候回到 SqlSession 了，让我们回顾一下 DefaultSqlSession 中最具代表性的 selectList 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84101480097587120000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public &lt;E&gt; List&lt;E&gt; selectList(String statement, Object parameter, RowBounds rowBounds) {
   try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
   } catch (Exception e) {
      // ...
   } finally {
      ErrorContext.instance().reset();
   }
}`, `84101480097587120000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;selectList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; statement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RowBounds&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;MappedStatement&lt;/span&gt; ms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMappedStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parameter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Executor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NO_RESULT_HANDLER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;ErrorContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;SqlSession 使用了经典的外观模式，背后真正执行查询操作的是 Executor，而 Executor 又使用了模板方法模式，它的 query 方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;94446662008290720000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   // ...
   // 强制清空一级缓存
   if (queryStack == 0 &amp;&amp; ms.isFlushCacheRequired()) {
      clearLocalCache();
   }

   List&lt;E&gt; list;

   try {
      queryStack++;
      list = resultHandler == null ? (List&lt;E&gt;) localCache.getObject(key) : null;
      if (list != null) {
         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
         // 查询数据库
         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
   } finally {
      queryStack--;
   }

   if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
         deferredLoad.load();
      }

      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
         // 如果是 STATEMENT 级，清空一级缓存
         clearLocalCache();
      }
   }

   return list;
}`, `94446662008290720000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MappedStatement&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RowBounds&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResultHandler&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheKey&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BoundSql&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 强制清空一级缓存&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryStack &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFlushCacheRequired&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;clearLocalCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      queryStack&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resultHandler &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; localCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;handleLocallyCachedOutputParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 查询数据库&lt;/span&gt;
         list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryFromDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      queryStack&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryStack &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DeferredLoad&lt;/span&gt; deferredLoad &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; deferredLoads&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         deferredLoad&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      deferredLoads&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLocalCacheScope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LocalCacheScope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STATEMENT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果是 STATEMENT 级，清空一级缓存&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;clearLocalCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述方法的实现逻辑有点复杂，首先我们来看最后一个 if 判断，可以看到如果一级缓存被设置为 STATEMENT 级，那么该方法会清空当前的本地缓存。这点跟前面的讨论一致。&lt;/p&gt;
&lt;p&gt;然后我们回到方法开头，这里会判断 queryStack 是否为 0，如果该值是 0 且当前的 SQL 语句中对 isFlushCacheRequired 参数进行了设置，那就会把 Session 中的 localCache 清空。请注意，Mybatis 一级缓存的启动过程不需要任何配置，它是强制开启的，我们无法关闭。但是通过动态 SQL 的 isFlushCacheRequired 参数可以强制清除所有一级缓存。&lt;/p&gt;
&lt;p&gt;通过查看定义，我们发现这个 localCache 就是一个 PerpetualCache 对象。如果这里没有从 localCache 获取到目标数据，就会调用 queryFromDatabase 方法查询数据库，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37455090942473830000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private &lt;E&gt; List&lt;E&gt; queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   List&lt;E&gt; list;
   localCache.putObject(key, EXECUTION_PLACEHOLDER);
   try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
   } finally {
      localCache.removeObject(key);
   }
   localCache.putObject(key, list);
   if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
   }
   return list;
}`, `37455090942473830000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryFromDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MappedStatement&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RowBounds&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResultHandler&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheKey&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BoundSql&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   localCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; EXECUTION_PLACEHOLDER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      localCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   localCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StatementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CALLABLE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      localOutputParameterCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，一旦完成数据库查询，我们会将从数据库中获取的数据保存在 localCache 中。而如果你查看 BaseExecutor 的 update、commit 和 close 方法，会发现这些方法在执行完毕之后都会清空一级缓存。&lt;/p&gt;
&lt;h3 id=&quot;mybatis-二级缓存&quot;&gt;&lt;a href=&quot;#mybatis-%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mybatis 二级缓存&lt;/h3&gt;
&lt;p&gt;通过前面的介绍，我们发现 Mybatis 的一级缓存本质上就是一个 HashMap。Mybatis 没有对 HashMap 的大小进行管理，也没有缓存更新和过期的概念。这是因为一级缓存只发生在一次请求或一次会话中，生命周期非常短，不需要添加复杂的控制逻辑。&lt;/p&gt;
&lt;p&gt;接下来让我们继续研究 Mybatis 中的另一种缓存表现形式，即二级缓存。相较一级缓存，Mybatis 的二级缓存使用方法有所不同，代码结构和类层关系也要更加复杂一些。&lt;/p&gt;
&lt;p&gt;与一级缓存不同，Mybatis 的二级缓存默认是不启用的，我们需要设置对应的配置项才能让它发挥作用。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;85443173010452600000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;setting name=&amp;quot;cacheEnabled&amp;quot; value=&amp;quot;true&amp;quot;/&gt;`, `85443173010452600000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;setting&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;cacheEnabled&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;下图展示了 Mybatis 中二级缓存的生效范围。请注意，二级缓存是与命名空间（Namespace）强关联的，即如果在不同的命名空间下存在相同的查询 SQL，这两者之间也是不共享缓存数据的。在 Mybatis 中，Configuration 管理着所有的配置信息，所以所有的二级缓存相当于全部位于 Configuration 之内，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-16-11-30-21-1a5fa20d4205afcce4f1418347211d8b-e5c7b.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 612px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 30.22875816993464%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABXElEQVQY0z2QWU/CUBSE+/+fcYlxiRvILrSUtlBKBEtZBAq0FNnBaMREDQKucTy9BB8m9/bkm9OZy5mmjOlExWiYJqlM49H6HPQVWC2BybYEtJo8hoMUYzfMRpOxiradAeeavr/KWC4KWC0Nprf5NX5/qnhfGbjtxGA1w2hbETTNIOaveQB1LN70f971fn4UcTfVwBV0P7qdOMpFLyplH1OvKzCz076kVBFUb/yokaxWlFgeZj1A9whKxvnaU/Kh3+VRNILgstoJVRPQ7wnrJHaUnkAh+AyNehDjYZLqRtFshMgksLueP8ZkJFH6OPthx4kTJyJ35QWnyAds+8tzDmYtAIcWPs2yyKh7MPRTqq8zY63qp7nGGDm5jdmjhof7NKqVC4wGSQohIaUcgSsaIeRzhxB4DyQCk+IWZGkHNlVyl4jCeu5KTHhY8o4TgyLvsm93niAmq+1T5TD+AGRPkys/H4WCAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 16 11 30 21&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-16-11-30-21-1a5fa20d4205afcce4f1418347211d8b-e5c7b.png&quot; data-srcset=&quot;/static/2024-12-16-11-30-21-1a5fa20d4205afcce4f1418347211d8b-ec56c.png 200w,
/static/2024-12-16-11-30-21-1a5fa20d4205afcce4f1418347211d8b-73c25.png 400w,
/static/2024-12-16-11-30-21-1a5fa20d4205afcce4f1418347211d8b-e5c7b.png 612w&quot; data-sizes=&quot;(max-width: 612px) 100vw, 612px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;回到代码，我们在 org.apache.ibatis.cache 包下试图寻找关于二级缓存的相关实现。但是，我们什么也没有找到。我们可以想象一下二级缓存实际上是跟 SQL Mapper 紧密相关的，因为命名空间位于 Mapper 中。所以，相关代码是否是与 Mapper 实现放在一起呢？答案是肯定的，我们在 org.apache.ibatis.mapping 包下找到了这么一个类：CacheBuilder。&lt;/p&gt;
&lt;p&gt;从命名上看，CacheBuilder 类的作用就是构建一个 Cache，这个过程通过它的 build 方法实现，该方法代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26111925027848405000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Cache build() {
   // 设置缓存默认实现
   setDefaultImplementations();
   Cache cache = newBaseCacheInstance(implementation, id);
   // 设置缓存属性
   setCacheProperties(cache);
   if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class&lt;? extends Cache&gt; decorator : decorators) {
         cache = newCacheDecoratorInstance(decorator, cache);
         setCacheProperties(cache);
      }
      // 对缓存执行装饰
      cache = setStandardDecorators(cache);
   } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
   }
   return cache;
}`, `26111925027848405000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 设置缓存默认实现&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;setDefaultImplementations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newBaseCacheInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;implementation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 设置缓存属性&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;setCacheProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PerpetualCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; decorator &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decorators&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newCacheDecoratorInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decorator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;setCacheProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 对缓存执行装饰&lt;/span&gt;
      cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setStandardDecorators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LoggingCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAssignableFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggingCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们先来看该方法的第一句，即调用 setDefaultImplementations 方法，该方法设置默认的缓存实现，这时候创建的实际上就是一个 PerpetualCache 类。然后，遍历装饰类接口，通过 setStandardDecorators 方法对 PerpetualCache 添加对各种装饰类的包装，从而构建出 PerpetualCache → LruCache → ScheduledCache → SerializedCache → LoggingCache → SynchronizedCache 这样一个装饰链，装饰器模式在这里得到了完美的应用，&lt;/p&gt;
&lt;p&gt;现在，各种缓存装饰类的创建方法有了，那么谁来用呢？让我们跟踪 CacheBuilder 类的使用者。在 Mybatis 中，只有一个地方调用了 CacheBuilder 类，这就是 MapperBuilderAssistant 类。&lt;/p&gt;
&lt;p&gt;在该类中存在一个 useNewCache 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;89550537185294350000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Cache useNewCache(
   Class&lt;? extends Cache&gt; typeClass,
   Class&lt;? extends Cache&gt; evictionClass,
   Long flushInterval,
   Integer size,
   boolean readWrite,
   boolean blocking,
   Properties props
) {
   Cache cache = new CacheBuilder(currentNamespace)
      .implementation(valueOrDefault(typeClass, PerpetualCache.class))
      .addDecorator(valueOrDefault(evictionClass, LruCache.class))
      .clearInterval(flushInterval)
      .size(size)
      .readWrite(readWrite)
      .blocking(blocking)
      .properties(props)
      .build();
   configuration.addCache(cache);
   currentCache = cache;
   return cache;
}`, `89550537185294350000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useNewCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; typeClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; evictionClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; flushInterval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; readWrite&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; blocking&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Properties&lt;/span&gt; props
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentNamespace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOrDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;typeClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerpetualCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addDecorator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOrDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;evictionClass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LruCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;flushInterval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readWrite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readWrite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blocking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blocking&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   currentCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，二级缓存被成功创建并保存在 configuration 对象中，接下来讨论如在查询过程中使用二级缓存。&lt;/p&gt;
&lt;p&gt;首先明确一点，在 Mybatis 中，如果开启了二级缓存，那么所有的 SQL 执行过程都将由 CachingExecutor 负责。顾名思义，CachingExecutor 是带有缓存机制的 Executor，我们对 Executor 已经有了足够的了解，所以直接来看它的 query 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;66488158026980650000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   // 获取缓存对象
   Cache cache = ms.getCache();
   if (cache != null) {
      // ...
   }
   return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}`, `66488158026980650000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MappedStatement&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; parameterObject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RowBounds&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResultHandler&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheKey&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BoundSql&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 获取缓存对象&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Cache&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cache &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ms&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parameterObject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rowBounds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resultHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; boundSql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在该方法体的第一行代码中，我们通过 MappedStatement 获取的 Cache 对象就是前面保存在 Configuration 中的 Cache 对象。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-20&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;关于 Mybatis 中缓存机制的面试题，一般都会考查所谓的一级缓存和二级缓存。从考点上，因为比较容易混淆，我们首先需要明确这两种缓存的区别，可以从缓存的作用范围进行记忆，作用范围小的是一级，反之是二级。同时，我们也要明确，这种面试题往往会考查实现原理而不仅是简单应用，因为在 Mybatis 中缓存的应用更多是被动触发的，而不是由开发人员主动进行控制的。&lt;/p&gt;
&lt;p&gt;Mybatis 中的一级缓存实际上就是一个 HashMap，并没有太多涉及到缓存管理方面的操作。同时，我们需要解释一下一级缓存与 BaseExecutor 之间的关系，在 Executor 执行具体的查询操作时，就会使用到一级缓存。如果缓存中找不到对应数据才会执行数据库查询。作为加分项，这里可以重点解释一下 Mybatis 中 CacheKey 的实现方法，这也是 Mybatis 框架本身在设计上的一大亮点。这些内容，确实需要结合源代码才能够理解的更加清晰明白，建议大家还是要阅读 PerpetualCache、BaseExecutor 等核心类中的代码执行主流程。&lt;/p&gt;
&lt;p&gt;相较一级缓存，二级缓存更为复杂，因为需要更多关注于缓存对象的生命周期以及具体的缓存策略。有时候，这个考点也可以和装饰器模式结合在一起进行考查。针对二级缓存，我们也是从应用方法上进行切入。二级缓存的启动需要我们通过配置项进行控制，而它的作用范围则取决于 Mybatis 的命名空间定义。&lt;/p&gt;
&lt;p&gt;在实现原理上，针对二级缓存的回答内容需要包含两个要点。第一个要点是通过装饰器模式来构建具体种类的缓存对象，这块我们可以结合设计模式相关内容进行展开；第二个要点是它与 CachingExecutor 之间的交互关系。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-18&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲内容关注于分布式系统构建过程中不可缺少的一个技术组件，即缓存。&lt;/p&gt;
&lt;p&gt;缓存是一个比较复杂的话题，也具有非常多的应用场景。针对应用层缓存，我们通常采用的是分级模式，从而更好地实现对缓存对象的存储和管理。同时，我们以 Mybatis 框架为例分析了它所具备的分级缓存模式。在 Mybatis 中，存在一级缓存和二级缓存。其中，一级缓存的作用范围较小，只能应用于请求级别或会话级别。而二级缓存具备更大作用范围，能够在应用程序级别确保目标对象得到缓存。我们对这两种缓存机制的底层实现原理都进行了详细的展开。&lt;/p&gt;
&lt;p&gt;从资源管理角度讲，缓存也是一种有效管理资源的技术手段。而除了缓存，业界也存在一些专门用来管理资源的技术组件，资源池就是其中的代表。那么，什么是池化操作？如何实现一个资源池？我们下一讲将对这些问题展开详细的讨论。&lt;/p&gt;
&lt;h1 id=&quot;资源管理：什么是池化操作？如何实现一个资源池？&quot;&gt;&lt;a href=&quot;#%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E6%B1%A0%E5%8C%96%E6%93%8D%E4%BD%9C%EF%BC%9F%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%B5%84%E6%BA%90%E6%B1%A0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;资源管理：什么是池化操作？如何实现一个资源池？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们介绍了缓存这一技术组件，并重点对多级缓存的设计思想和实现策略进行了详细的展开。本质上，缓存体现的是一种对资源利用效率进行合理管理的技术手段。&lt;/p&gt;
&lt;p&gt;资源管理对于软件系统开发而言是一个很大的话题，同时也是一个很通用的话题。在本讲内容中，我们继续围绕资源管理这一话题进行展开，并聚焦于资源池这一技术组件。&lt;/p&gt;
&lt;p&gt;那么，什么是池化操作？如何实现一个资源池？让我们带着这些问题来学习本讲内容。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-18&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-18&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;对于任何类型的应用程序而言，开发人员所处理的对象本质上都是各种资源（Resource）。所谓资源，在软件架构设计和实现过程中有很多表现形式，例如数据库会话、网络连接、分布式服务和组件等都可以认为是系统的资源，都需要进行管理，而性能、可伸缩性、灵活性则是资源管理的基本需求。&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;li&gt;你知道 Mybatis 是如何有效管理数据库连接的吗？&lt;/li&gt;
&lt;li&gt;你能简要描述 Mybatis 中对数据库连接对象 Connection 的管理过程？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然具体的资源池表现形式有多种，但基本的组成结构和实现原理大同小异。接下来，我们围绕这些问题来对资源池的概念和特性做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-19&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;在计算机编程领域中，池（Pool）是一种对资源的抽象方法，代表一组可以使用的资源，但这些资源不能随时被创建和释放。在架构模式中，也存在一种资源池（Resource Pool）模式。客户端向资源池请求资源，在获取到资源之后就可以用它来完成特定的任务。一旦任务完成，这个资源又可以被回收到资源池继续进行使用。&lt;/p&gt;
&lt;p&gt;一个典型的资源池的组成结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-17-11-35-43-adea505ad97e6e0ee56ba27d5620ccea-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.21329211746522%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAAB9klEQVQoz12Sa0/iUBRF+f//Y77MoPIQRV4C4ogjIhTBgmBLaXlneBShLZQ1BxKNzkluzk3au7LP2TsQbyaJGikiWoLIW4K4lSVcD/PSytG3qnLK9PsqrrfF931m8zlPvR6Pgz4PpokyGLBerznUfr8nEG3FaM/yNPopWqMM5WGaYOME9TVOtRLluRal3S6yWCzZbDaYlsVlvU608cJppUqm08FeLr8A1RhNAd4oQUqv5zwNM1zNs1TN+vGnv7O5KHD4VvLQ327Z73aw8799CiSta9ICDHViXJgJcsvfZDe3qJPmx2uWywWTyYjt1sHzNnLcT4ArYMfzjn3jugTOaiEsGbPVjGBolzT0OAlRqI6bn49se0FNCVF5+iUrOEGpRugZJpquE6xU+Ckr+PFY5rzbFYVmhrx9y5ka5sKIU1gVuV4XaE5bX2cUc9roWl2U6kynpqjd4YgibTymMxrRHg7Rp1MCMVl+bZrlj3ZBxUpSkh2m7DwlXWHvIeOuxZCVGOJ8sPFdj53jSHf5vwIR9ZxnUVYUU8ovIW5F6cHlrpXD0AsY3RyKkpGxBfq+omT2yEpc8hKdG7lPJEaH2kmk/IPL6V6Ou22Zq+E1pxKhm/cHkpZESMuhde4xew+yr9oRuJJ4VAVWMAwKbxp30me2fQQeYIfY/AN0ZY4vLYmN3AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 17 11 35 43&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-17-11-35-43-adea505ad97e6e0ee56ba27d5620ccea-a40ca.png&quot; data-srcset=&quot;/static/2024-12-17-11-35-43-adea505ad97e6e0ee56ba27d5620ccea-3426a.png 200w,
/static/2024-12-17-11-35-43-adea505ad97e6e0ee56ba27d5620ccea-f3a4f.png 400w,
/static/2024-12-17-11-35-43-adea505ad97e6e0ee56ba27d5620ccea-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;资源池的主要特点在于节省了创建资源实例的开销和时间，但存储空间会随着对象的增多而增大。当我们面对资源池相关的面试问题时，我们首先需要基于日常开发过程中最常见，或者是自己平时最擅长的一种具体的资源池进行展开。例如，任何一个应用程序都需要访问数据库，也就需要引入数据库连接池（Connection Pool）。连接池就是一项适合进行深入挖掘的资源池技术。我们需要明确它的基本工作流程以及核心功能特性。&lt;/p&gt;
&lt;p&gt;一旦明白了连接池等具体资源池的工作流程和功能特性，我们就可以基于具体某一款开源框架来深入分析底层的设计方法和实现原理。这部分内容同样需要候选人平时有足够的积累，并在面试过程中能够以自己的语言进行总结和提炼。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-21&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;在接下里的内容中，我们将基于 Mybatis 框架来对连接池这一代表性的资源池展开讨论。在介绍 Mybatis 数据库连接池之前，我们有必要对连接池的实现机制有个总体的把握。因此，我们先来全面梳理连接池的工作流程和核心要素，然后在此基础上详细阐述 Mybatis 中对数据库连接对象 Connection 的获取过程。&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;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-17-11-36-44-7a3abfe8d257fc53ef7890fe3d2dc082-9e167.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 538px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 90.33457249070632%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAACSElEQVQ4y41Ta2/aUAzN//8Z+zBN24d2k9jWVtM0de1UIANawiNAQ2AJULIkBEgCeZzZF8JSBC2WrJtcOyfHx7YEsjRN+IBhaKhV3+PX7RvI5beQ5Q9wHAubnPQkl7JkNm9mYzSqo9MpoqPe4c/wAfO5hyRJEccx8rnHTMoS8onrNbN9gut6mEwmGA6H4txneoi5lL/MrFq9RrMpb9+OMzrEVtqAJSLo+0ssFktomgK938ZqtcZyuSQdbdi2LVg6jiNO13VxiIyUR7ftGTxvLp7jOIVlWQLU93269wTIYrEQoPyjg4C6fk961dDrFVGpXFHyeBdktt1ui0CD00tut66gtguoVs4I9DNMo0Vd3QSjKEGpdEl6lsV7kiRHm7IDnM9ZI5fKmdE5243HRjtHlMwyrKn1XG6e3dE5zIznjQE2jekTqL/VM4Gq9khjl+4CAo9en8MwjNDt3KD+cEbbck6b8o4kKImY9ihT17/ivnaOp8kPKMo3TKd/iUBMBFbCgyAgiaL/gMzOMFTS64bYFAn8DqbZE7HxSMNkXBfbU/l9SRvUFOwZgJ1lYsAwDJ+v3imd9DyeVZ9GKRTzaJomETGExs8YZgPOncz8UIyNV7HRkBHQIvBdxpJBpZeY7Y9GFK1FabZtkcYXxG5ATAMCi4SzlhJesf2SecwYhK/9IBTlZ8bbdRJDa2piOFAw0BVi9p2YPW43aUYLcY12q0zxBnSKSy8zS8XW6H3+oAC19ZE6foHB4CeVl2A87lDHv1D3P0HXCvSDW/wDuT5rwp5dW+AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 17 11 36 44&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-17-11-36-44-7a3abfe8d257fc53ef7890fe3d2dc082-9e167.png&quot; data-srcset=&quot;/static/2024-12-17-11-36-44-7a3abfe8d257fc53ef7890fe3d2dc082-bd273.png 200w,
/static/2024-12-17-11-36-44-7a3abfe8d257fc53ef7890fe3d2dc082-7e34d.png 400w,
/static/2024-12-17-11-36-44-7a3abfe8d257fc53ef7890fe3d2dc082-9e167.png 538w&quot; data-sizes=&quot;(max-width: 538px) 100vw, 538px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;围绕上述介绍的连接池的管理方式，我们可以抽象出一些控制参数，常见的参数包括最小空闲连接数（MinIdle）、最大空闲连接数（MaxIdle）、连接池最大活跃连接数（MaxActive）、最大超时时间（MaxTimeout）等。&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;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;介绍完连接池的基本概念之后，接下来我们将花较大的篇幅来介绍 Mybatis 中的数据库连接池，以帮助你理解池化技术在开源框架中的实际应用。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-19&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;与上一讲介绍 Mybatis 缓存时采用的自下而上（即从底层实现类 PerpetualCache 出发向上游逐层分析直到系统的访问入口 SqlSession）的分析方法不同，我们将采用自上而下的策略来介绍数据库连接池，即从如何获取连接 Connection 对象开始逐步分析其背后的池化操作。&lt;/p&gt;
&lt;p&gt;我们在介绍 Mybatis 缓存时，已经明确了 DefaultSqlSession 会调用 CachingExecutor，然后 CachingExecutor 是 BaseExecutor 的实现类，而 BaseExecutor 实现了 Executor 接口，该接口提供了多种用于数据 CRUD 的方法。有了这层调用链，我们可以想象，为了获取与数据库访问相关的 Connection，入口应该位于 BaseExecutor 中的几个与数据库访问直接相关的方法中。&lt;/p&gt;
&lt;p&gt;我们直接来到 BaseExecutor 的子类 SimpleExecutor 中的 doQuery 方法。在该方法中我们看到了一个 prepareStatement 方法，进一步猜想获取 Connection 应该是在这一方法中。让我们来看一下这个方法的实现过程，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18206733540422904000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
   Statement stmt;
   Connection connection = getConnection(statementLog);
   stmt = handler.prepare(connection, transaction.getTimeout());
   handler.parameterize(stmt);
   return stmt;
}`, `18206733540422904000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Statement&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;prepareStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StatementHandler&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Log&lt;/span&gt; statementLog&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Statement&lt;/span&gt; stmt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statementLog&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   stmt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   handler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parameterize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stmt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; stmt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;果然，在这里我们看到了获取 Connection 对象的 getConnection 方法，该方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43783057789836950000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected Connection getConnection(Log statementLog) throws SQLException {
   Connection connection = transaction.getConnection();
   if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
   } else {
      return connection;
   }
}`, `43783057789836950000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Log&lt;/span&gt; statementLog&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statementLog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDebugEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConnectionLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; statementLog&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; queryStack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们发现 getConnection 实际上是 Transaction 接口的一个方法。除了 getConnection 方法之外，Transaction 作为事务类还包含 commit 和 rollback 等事务处理相关方法。在 Mybatis 中，Transaction 接口有两个实现类，即 JdbcTransaction 和 ManagedTransaction。无论哪种方案，我们在各自的 getConnection 方法中都能看到如下所示的语句：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15753294316735179000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`this.connection = this.dataSource.getConnection();`, `15753294316735179000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，我们是通过 DataSource 来获取 Connection 对象。以执行查询操作的 query 方法为例，获取 Connection 对象的整体工作流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-17-11-38-25-958935f9b16ae8c25620326e655f7577-32a72.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 350px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 126.85714285714285%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAAsSAAALEgHS3X78AAAEnElEQVQ4y41VaVMiSRTs//8PdscZYx0Hj1GRs4EGAWnlauQ+upvmVPACGcdbnNysAid2IvaDRmQUVf2q6tXLl6mSOnQBGBItooOnhwxs04O25cXpMIxW3Y3TQQjN+g563QDXQhifRTCdJBhvEjZhEX10uxoUTdtiYAi2FYTTVlEq7kALf0YkvCKRSq5zvkJ8QjT6BVHtC5KJNeQy27BaAbRtVe7tOirSx24ok8kUrWYLtm3Dsiwe6qDf72FA9HtdDIcDVCplxjTQ6bRhmi00GjU5yj2mxQMtecZ4fA4FH/i7uprg8fEZNzcz5PMFNJsmpvz9f39Kr9dBLqujUMigYKQXKKRRLudwwrV67QS7uxvY2vyKcimPbDYl13M5HQZjjeUesW6aTShR7Rse7o8xvU7iZnqI2c0hrq/irI8H5+Mohv0QOm0/Ls5jcn52GsHoTGNcSsYLTCdJ3P3UUTzxQIkfrDPRJnFClIgy3uZ5JA7+RurwM0n5wsBNvkJkuMXCr/KyfcZViOISYi/r2gxCMQwduu7F8XEAx0cCfm4K8AmqnGcyIbLqRiK+z/ppODmJybVclsiFYXAtlw0jm1FZqtTHSJlOZ3h+ecX19ZQX5SUptu2wK9qoVuuo1RtybTy+gDKfv3HDD9bkliz+iclkhp8/H5BM6sik86jXW3xNGh2nx9Yx0W53USpVeZjFgxu4vLyCoushsuRl6j4ytkTeyzUfKmUVjXoER0fbOExuMKMEzNYBmz/Ig1Q5Vsoh1lYlgnx6BEoisS5lsyDGXEqQUvpVYPf7MBwIqWkYkd225cYZpbcg5D1WoEE47IawkN4mnxDkzX52fYBYjK2GD/HYKg6IUPAvREKfENNWpOya/Na2FrG23BOgwgIkcw/KlNITkus4Dpz/QMwHgz5GozPWriabdjwe0QA6qFWrco+Qnm3Z/Gai1RLSG3+U5SleX1+lVqOxGC/s8JDFgYZhoMILarUaLx9Bmc1m6EkT6MuM3nFKUxDj+fkY4XAI4ZDKrEQmDWbpkGFbZus4bb6mzUxNXFywbVR1nRbko1t4f8NsefmsPdSrbv4WbO/SD/dZp+Cydj4J8e19dChPPfWdLMfXlwZZWrInUMf1ZRzFwgaO9VW20DeUi5skYxdFwyW1D1SlTBcoSaYtS4WSSoUlO5m0Z4l9yshDHe/QTDegRVxkeQ1q8B/E46Ifd9lvPl4SYL/65VgwgrKPM+nwgpSXlzcW/U/M57/w9vZLkiIkd3f3gPv7B9atR/RZ8zNiRDM+pfH2ycNA+uWHWLbtDhk85+YhZZiSm206uxhLpQqJsiSk9MrlPJ1G5TM0Cj/yG/mcxmdEaf8pBAMueD1rJCdJzaYouQRdJy5RKiYlCkaMppwVBrvGHPLSIB/uj2TBnx7TuLzQKD0v+l0/5adSemE4tkfOb2eHMvb+TkDH3a2O+WsW1TINNhZzSZaenvJ4eTYk5q8Fujazqeww029LuPifziXN9vbHkYx5eV7seeZeYbSVsg9KpVLC9+2v2Hdvwr23sYRLzr2ebfj9OwgQft/33xDf9hjzHi/m21tr7I4j/AuGe9vbsdxOlwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 17 11 38 25&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-17-11-38-25-958935f9b16ae8c25620326e655f7577-32a72.png&quot; data-srcset=&quot;/static/2024-12-17-11-38-25-958935f9b16ae8c25620326e655f7577-47adc.png 200w,
/static/2024-12-17-11-38-25-958935f9b16ae8c25620326e655f7577-32a72.png 350w&quot; data-sizes=&quot;(max-width: 350px) 100vw, 350px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&quot;pooleddatasource&quot;&gt;&lt;a href=&quot;#pooleddatasource&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;PooledDataSource&lt;/h3&gt;
&lt;p&gt;DataSource 是 JDBC 中定义的接口，Mybatis 实现了该接口，并提供了三种实现方案，相关的工厂类和实例类类层关系如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-17-11-39-19-b5e44e1339138e81a77837f16e603a64-0f06a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 643px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 39.03576982892691%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABpUlEQVQoz4VSa0/CQBDsX/ePqGDwGVE/qFFR64M3LX0BQi1QKLaCCokKFFrGscT4TS+Z5G5vb3dm9gSzcQ1F3oeuJeE4KjzXQrdzDlU5ZDyJVvMU3a6CBYDFIiQWf0KoGCfI3K9CKsZZSCJqUMoJZNOxCKVCDI9mBj9r+TBcIgx/90TIs/Ds2RgNHViPGiyrgeHwFdVqKWLqeS3Ydg2u62AymWE69fHfEn42/X4Lhn6GWjWFZjPLAj6eegob3TJ+Qekieo6M8XgMu62haUm8k5gro90qR5BlEUIQzKOCHcrV1A0Y2hbqD0cYDPr09QBqOU4/NxnbRbVyHMUL+W0ijmwmhnwuTrsSEdbXVn4ZPvU6lKxQZh2mqcKfBZFk120QJgdT4dBMBEFA9u8I5hNa8IGXFw/z+Ri+/4m3twEEQ78kkxRlpVjUZPIUun5NNleoGDf084ZDKdDwZeMZG9ltFQ81kfciLRJRr98xJw2na0EoS3uQSnuQpW0G8xiNXpG+26CEHeSyCRTzmygVk2QwjQpOOBjDSPEnLN9p6rctSZI6hKbl8AUfEUGZkxncNAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 17 11 39 19&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-17-11-39-19-b5e44e1339138e81a77837f16e603a64-0f06a.png&quot; data-srcset=&quot;/static/2024-12-17-11-39-19-b5e44e1339138e81a77837f16e603a64-66246.png 200w,
/static/2024-12-17-11-39-19-b5e44e1339138e81a77837f16e603a64-2dbe7.png 400w,
/static/2024-12-17-11-39-19-b5e44e1339138e81a77837f16e603a64-0f06a.png 643w&quot; data-sizes=&quot;(max-width: 643px) 100vw, 643px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，我们重点要介绍的是 PooledDataSource，但也会涉及一部分 UnpooledDataSource 的内容，因为 PooledDataSource 也是构建在 UnpooledDataSource 的基础之上。&lt;/p&gt;
&lt;p&gt;在 PooledDataSource 类中，首先我们看到的是一组连接池的参数，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13925618536903993000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = &amp;quot;NO PING QUERY SET&amp;quot;;
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;`, `13925618536903993000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolMaximumActiveConnections &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolMaximumIdleConnections &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolMaximumCheckoutTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolTimeToWait &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolMaximumLocalBadConnectionTolerance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; poolPingQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;NO PING QUERY SET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; poolPingEnabled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; poolPingConnectionsNotUsedFor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里出现了最大空闲连接数（poolMaximumIdleConnections）、连接池最大持有连接数（poolMaximumActiveConnections）、最大存在时间（poolMaximumCheckoutTime）等核心参数，我们在前面介绍连接池的工作流程时都提到过这些参数，然后我们来看 PooledDataSource 的 getConnection 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30758553231131636000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Connection getConnection(String username, String password) throws SQLException {
   return popConnection(username, password).getProxyConnection();
}`, `30758553231131636000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;popConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProxyConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们继续跟进，来到这里的 popConnection 方法，该方法非常长，但结构比较清晰。如果我们对连接池的管理方法有一定了解的话，理解这段代码难度并不大。为了更好地把握代码结构，我们对该方法的代码进行裁剪，只关注于主要的分支和流程，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13026952469876062000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private PooledConnection popConnection(String username, String password) throws SQLException {
   while (conn == null) {
      synchronized (state) {
         if (!state.idleConnections.isEmpty()) {
            // 如果 idle 列表不为空，表示有可用连接，直接选取第一个元素
            conn = state.idleConnections.remove(0);
         } else {
            // 连接池没有可用连接的场景
            if (state.activeConnections.size() &lt; poolMaximumActiveConnections) {
               // 如果 active 列表没有满，直接创建新连接
               conn = new PooledConnection(dataSource.getConnection(), this);
            } else {
               // active 已经满了
               // 获得使用最久的连接，判断是否已经超时
               PooledConnection oldestActiveConnection = state.activeConnections.get(0);
               long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
               if (longestCheckoutTime &gt; poolMaximumCheckoutTime) {
                  // 已经超时，将原连接废弃并建立新连接
                  conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
               } else {
                  // 如果没有超时，则进行等待，并计算时间累加
                  state.wait(poolTimeToWait);
               }
            }
         }
         if (conn != null) {
            // 如果获取到了连接，则验证该连接是否有效
            if (conn.isValid()) {
               // 如果连接有效，更新该链接相关参数和状态
            } else {
               // 如果连接无效，且无效连接数量超过上限则抛异常
            }
         }
      }
   }

   if (conn == null) {
      // 如果最终无法获取有效连接，则同样抛异常
   }

   return conn;
}`, `13026952469876062000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;popConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;idleConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果 idle 列表不为空，表示有可用连接，直接选取第一个元素&lt;/span&gt;
            conn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;idleConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 连接池没有可用连接的场景&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; poolMaximumActiveConnections&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 如果 active 列表没有满，直接创建新连接&lt;/span&gt;
               conn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// active 已经满了&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 获得使用最久的连接，判断是否已经超时&lt;/span&gt;
               &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt; oldestActiveConnection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; longestCheckoutTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; oldestActiveConnection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCheckoutTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;longestCheckoutTime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; poolMaximumCheckoutTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token comment&quot;&gt;// 已经超时，将原连接废弃并建立新连接&lt;/span&gt;
                  conn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oldestActiveConnection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token comment&quot;&gt;// 如果没有超时，则进行等待，并计算时间累加&lt;/span&gt;
                  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolTimeToWait&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果获取到了连接，则验证该连接是否有效&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 如果连接有效，更新该链接相关参数和状态&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;// 如果连接无效，且无效连接数量超过上限则抛异常&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果最终无法获取有效连接，则同样抛异常&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; conn&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上述代码中，我们添加了很多注释来解释用于获取 Connection 的 popConnection 方法的整体流程，而不关心具体细节，整体流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-17-11-40-42-ac05af9cf25831f81f35a8af793b8821-92ba0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 636px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 68.23899371069182%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACFklEQVQ4y41UC3OaQBDm//+NNFPTx3SaadrOOG06tU18hSRGK/IKeoAGQQRRfIBfl2NiNCaZ3MzC3d7ex+633yGARhCM0JFOcNP4hNLvA/wpvcH52Vso8heo6t8sBOt1itcMIXssFnN4roGho8OyOrjVbyDLInq9JkymYD5fZJAEmlv+gYf5DuBj53K5gqapGA49RFFMNkMcxzsxTwHtAGblJMmKB1qWgXq9SFkvuC+KJphOpxu7B382w+3FeBzSgSWfu64Lz/PIfDiOw+eZLwiClwFNsw3LbBFvl2g1zyjDFnx/wDcZM9C39b1D92DbfG4ApXaRunsMVSlC135QM76h35f5puPYkKQqlR09y9tjv5CSGpJkjew9my2opIiXqSgqb1BefrbWEIYRxcyJy7xZabqfqbCtMcau0f53TPr7Tll/xmQyxqAvESWnRMcJpPZXGLc/0TVOiYoSRp69x6ewvRiNBlyDjEmkP4nrc+zfoddtkDVpr0HUiBDFX3QRKrBtxpuUJMlDhk8Ru8XQzkrTZOLX5koIwykH8/3RRnJZpcJrxJpzFkPuXMA0M25TLFfJS8LOwYLQpRtiUFMYWY8oMAlAhq43KTOVmpByGhjrkCKasC0FPsW4bo+uLsXTuc1NyYailFE+P0C1XECtUsBF7R3q1SPUyK7E9/SRAUmpi0r5kHwF/jO5vvqIeu2Izh3iUvyA/+81KLYdPOXiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 17 11 40 42&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-17-11-40-42-ac05af9cf25831f81f35a8af793b8821-92ba0.png&quot; data-srcset=&quot;/static/2024-12-17-11-40-42-ac05af9cf25831f81f35a8af793b8821-cf954.png 200w,
/static/2024-12-17-11-40-42-ac05af9cf25831f81f35a8af793b8821-955d7.png 400w,
/static/2024-12-17-11-40-42-ac05af9cf25831f81f35a8af793b8821-92ba0.png 636w&quot; data-sizes=&quot;(max-width: 636px) 100vw, 636px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在整体流程上，当 Mybatis 执行查询时会首先从 idleConnections 列表中申请一个空闲的连接，只有当 idleConnections 列表为空时才会创建新连接。当然，PooledDataSource 并不允许无限创建新连接。当连接池中连接达到一定数量时，即使 idleConnections 列表为空，也不会再继续创建新连接，而是从 activeConnections 列表中找出使用最久的一个连接，判断其是否超时。如果超时，则将该连接废弃并创建新连接，否则线程会一直等待直到连接池中有新的可用连接。&lt;/p&gt;
&lt;p&gt;但是，我们还是注意到这里有一个 PooledConnection 类，通过 PooledDataSource 获取的连接就是这个类。该类是 Mybatis 自己设计的一个 Connection 类，让我们来看一下。&lt;/p&gt;
&lt;p&gt;在 PooledConnection 中我们首先应该注意到的是它的两个变量，即 realConnection 和 proxyConnection，它们的类型都是 Connection。从命名上看，其中的 realConnection 是真正通过 JDBC 驱动类建立的连接，而 proxyConnection 是一个代理连接，也是 PooledDataSource 返回的连接。&lt;/p&gt;
&lt;p&gt;PooledConnection 本身就实现了 JDK 的 InvocationHandler 接口，并提供了如下所示的 invoke 方法实现：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;7262971974372134000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   String methodName = method.getName();
   if (CLOSE.hashCode() == methodName.hashCode() &amp;&amp; CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
   }

   try {
      if (!Object.class.equals(method.getDeclaringClass())) {
         checkConnection();
      }
      return method.invoke(realConnection, args);
   } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
   }
}`, `7262971974372134000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; methodName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CLOSE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; CLOSE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pushConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclaringClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;checkConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;realConnection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExceptionUtil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrapThrowable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的逻辑主要是 DataSource 的 pushConnection 方法。我们采用与 popConnection 方法相同的方式来介绍 pushConnection 方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;86005221528483870000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected void pushConnection(PooledConnection conn) throws SQLException {
   synchronized (state) {
      // 从 active 列表中移除该连接
      state.activeConnections.remove(conn);
      // 如果是有效连接
      if (conn.isValid()) {
         // 如果满足 idle 列表没有填满且类型符合期望
         if (state.idleConnections.size() &lt; poolMaximumIdleConnections &amp;&amp; conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
            state.accumulatedCheckoutTime += conn.getCheckoutTime();
            // 则创建一个新的连接并放入 idle 列表中
            PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
            // 唤醒线程
            state.notifyAll();
         } else {
            // 如果不满足判断条件，则说明该连接已经不需要存储，直接关闭真正连接
            conn.getRealConnection().close();
         }
      } else {
         // 如果是无效连接，则记录
      }
   }
}`, `86005221528483870000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pushConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt; conn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 从 active 列表中移除该连接&lt;/span&gt;
      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果是有效连接&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果满足 idle 列表没有填满且类型符合期望&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;idleConnections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; poolMaximumIdleConnections &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnectionTypeCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; expectedConnectionTypeCode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;accumulatedCheckoutTime &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCheckoutTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 则创建一个新的连接并放入 idle 列表中&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt; newConn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 唤醒线程&lt;/span&gt;
            state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notifyAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 如果不满足判断条件，则说明该连接已经不需要存储，直接关闭真正连接&lt;/span&gt;
            conn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 如果是无效连接，则记录&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结合 popConnection 方法，pushConnection 方法的整体执行流程也在意料之中。这里唯一需要注意一点在于 state.notifyAll 语句，该语句与 popConnection 方法中用于等待线程的 state.wait 语句相对应，从而唤醒了线程。这样，当 SQL 执行完成时，PooledDataSource 也不会直接关闭线程，而是将其加入到 idleConnections 中并唤醒所有等待线程。&lt;/p&gt;
&lt;h3 id=&quot;unpooleddatasource&quot;&gt;&lt;a href=&quot;#unpooleddatasource&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;UnpooledDataSource&lt;/h3&gt;
&lt;p&gt;接下来我们来讨论 UnpooledDataSource。在 Mybatis 中，PooledDataSource 实际上也是依赖 UnpooledDataSource 来创建真正的连接，这点通过 PooledDataSource 类的构造函数可以得到印证，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;91807545520383570000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public PooledDataSource(String driver, String url, String username, String password) {
   dataSource = new UnpooledDataSource(driver, url, username, password);
   expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}`, `91807545520383570000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; driver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   dataSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UnpooledDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;driver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   expectedConnectionTypeCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;assembleConnectionTypeCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所以，PooledDataSource 中的 dataSource 变量实际上就是一个 UnpooledDataSource 对象。在 PooledDataSource 中 的 popConnection 方法中，我们看到如下所示的语句：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72535007469773636000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`conn = new PooledConnection(dataSource.getConnection(), this);`, `72535007469773636000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;conn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PooledConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里通过 UnpooledDataSource 获取了 Connection 并构建 PooledConnection 对象，所以前面提到的 PooledConnection 中的 realConnection 对象实际就是来自 UnpooledDataSource。&lt;/p&gt;
&lt;p&gt;我们有必要进一步分析一下 UnpooledDataSource 中获取 Connection 的过程。我们跟踪 UnpooledDataSource 的 getConnection 方法，找到如下所示的 doGetConnection 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45964762971392336000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private Connection doGetConnection(Properties properties) throws SQLException {
   initializeDriver();
   Connection connection = DriverManager.getConnection(url, properties);
   configureConnection(connection);
   return connection;
}`, `45964762971392336000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doGetConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Properties&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;initializeDriver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Connection&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DriverManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;configureConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里回归到了熟悉的 JDBC，实际上就是基于 JDBC 中的 DriverManager 工具类来获取 Connection。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-21&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;以我的经历，有时候在面试时会出现类似 &lt;code class=&quot;language-text&quot;&gt;如果让你实现一个简单的资源池，你会怎么做？&lt;/code&gt; 这种开放式问题。这类问题对于面试者而言有利有弊，有利的点在于本身就没有标准答案，自由发挥的空间比较大，很多时候可以做到自圆其说。不利的点在于需要有比较好的反应能力，能够快速地针对某个具体问题给出灵活的解决方案，而不是像存粹考查知识体系的问题那样给出固定的回答即可。&lt;/p&gt;
&lt;p&gt;针对这道开放式问题，在回答上一般会有三个要点。我们首先需要简要阐述资源池的作用和基本结构，这块偏理论知识，点到就好。然后，我们明确对于池化操作而言，最终都需要有个存储容器来保存池化最新，所以需要在 JDK 容器中选择一种作为方案。最后，我们需要设计如何对资源池中对象进行操作的方法，这些方法需要考虑到线程的安全性。上述三个要点对于类似的问题都是可以直接套用的。&lt;/p&gt;
&lt;p&gt;针对具体的开发框架，面试官经常可能会抛出类似 &lt;code class=&quot;language-text&quot;&gt;简要描述 Mybatis 中对数据库连接对象 Connection 的管理过程？&lt;/code&gt; 这样的问题。这种问题问的是 Connection 的管理过程，本质上还是在考查大家对连接池的理解程度，因为 Connection 的管理就是通过连接池来完成的。对于普通的应用场景而言，Connection 的管理对于开发人员是透明的，所以该题的考点有一定难度，需要面试者对 Mybatis 内部实现原理有深入的理解。&lt;/p&gt;
&lt;p&gt;在解答思路上，我认为这道题的主要挑战是梳理 Connection 在使用上的整个流程，该流程涉及到两大方面，即获取 Connection 和释放 Connection。为此，Mybatis 中分别提供了 popConnection 和 pushConnection 方法。这两个方法是需要明确点到的，这是一个要点。另一方面，在连接池的管理上，获取和释放 Connection 的过程并不是很复杂，无非就是要做很多判断和异常处理，但如何确保并发访问下新连接的创建和访问是一个难题，需要指出 Mybatis 在多线程处理上的实现方式，这是该问题的第二个要点。&lt;/p&gt;
&lt;p&gt;本讲详细介绍了 Mybatis 中 PooledConnection 类的设计思想和实现方法，同时也给出了 popConnection 和 pushConnection 这两个核心方法的流程细节，方便你进行学习。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-19&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲主要关注于资源池模式本身的概念、模型和实现过程，并引出了数据库连接池这一典型的资源池应用方式。和上一讲一样，我们结合 Mybatis 框架具体分析了该框架中如何实现数据库连接池的设计理念和详细过程。&lt;/p&gt;
&lt;p&gt;从下一讲开始中，我们将转换视角，讨论与系统扩展性相关的话题。扩展性是一个很通用的话题，涉及面很广。下一讲，我们先来考虑开发框架本身的扩展性，我们将围绕框架与框架之间的集成过程来分析扩展性的表现形态和实现方式。&lt;/p&gt;
&lt;h1 id=&quot;框架集成：如果需要实现两个框架之间的集成，有什么办法？&quot;&gt;&lt;a href=&quot;#%E6%A1%86%E6%9E%B6%E9%9B%86%E6%88%90%EF%BC%9A%E5%A6%82%E6%9E%9C%E9%9C%80%E8%A6%81%E5%AE%9E%E7%8E%B0%E4%B8%A4%E4%B8%AA%E6%A1%86%E6%9E%B6%E4%B9%8B%E9%97%B4%E7%9A%84%E9%9B%86%E6%88%90%EF%BC%8C%E6%9C%89%E4%BB%80%E4%B9%88%E5%8A%9E%E6%B3%95%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;框架集成：如果需要实现两个框架之间的集成，有什么办法？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们分析了以数据库连接池为代表的资源池的设计思想和实现方式。而在本讲内容中，我们将讨论一个非常常见但又不被开发人员所感知的技术组件，这就是框架集成组件。这里的框架集成，指的是第三方框架与 Spring 框架之间的集成。在现实开发环境中，无论使用的是 Dubbo 还是 Mybatis 框架，你会发现它们的使用方式和 Spring 框架是完全一致的。&lt;/p&gt;
&lt;p&gt;作为一个通用型的开发框架，Spring 为我们提供了非常强大的扩展功能。那么，这些扩展功能是如何运作的？如果需要实现两个框架之间的集成，我们又有什么办法呢？这是一个实战性非常强的话题，本讲内容将围绕这一话题展开讨论。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-19&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-19&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在 Java 的世界中，我们知道 Spring 是当下最主流的开发框架，没有之一。而在使用 Dubbo、Mybatis 等开源框架时，我们发现可以采用和 Spring 完全一样的方式来使用它们，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-39-02-5675f1895f17a288490199d49fd1f2f4-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70.78825347758887%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACBElEQVQ4y4VT227aQBD1/6tVpD72oS9VWlVVoVCggbZKIqVRoSHgGAfbcYyNufgaEEm5JLZPZ5cYoRArIx3NWJ6Z3TNnVsATS5KY+9HIxoWYhyTmcN78DLH1BbKUQ6tVxcNDTHnJBuu6dSxkNRwO++heFtGR8miefUKbGl5pJcjyL+zWJJtYyPrJ/Hw+x3K5hOd5cF2Xg8WDwQCO4yAMQ8xms5du+NiQcHt7h9VqBUVRYBgGfN8nBHT7IW/MGk6n0+yG2zNhlDW1Qiij/ucDUS/AGR+Sf4HyNsXtGY5HA6hKiQvy88c7HHx/i2u9BLFdQxQl2aLEccybMJ/GjHAURej3bU6TGftmswuCkOexnLQueaxj2Jkhs/v7mIvBjM1J0zQ+w3Rei8WCcqLnSiEo3QaM6wZ0vQHLPCeavyFdnHAxmDEV12L4JNJaUXaYLJ9S7gnM3hlMk9D7Sz3qEBr1ffrIo1LeQ636Bna/SPuXgx8EyLIwvIHSLdCyv0ex8Arfiq9JrI/wvRoEy1KpSRc9o8Oh6yK9hlNOK8vuaJ3a7Tr0K5HyWY1El7qEZSnPz5AJF9ANVVUnqjdE+x/RncO2R+j1LEwmk8zDhFTdDaL1sF13TCtTICoVyJ0q+QN6fl/RbNbSY7FTSxCe7lH6PbBNuM4RPPeIqJX5u/bcQ4qPNwpv16T4D6tuIyqEMq6tAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 39 02&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-39-02-5675f1895f17a288490199d49fd1f2f4-a40ca.png&quot; data-srcset=&quot;/static/2024-12-18-10-39-02-5675f1895f17a288490199d49fd1f2f4-3426a.png 200w,
/static/2024-12-18-10-39-02-5675f1895f17a288490199d49fd1f2f4-f3a4f.png 400w,
/static/2024-12-18-10-39-02-5675f1895f17a288490199d49fd1f2f4-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可能你在平时的使用过程中并没有意识到这一点，但仔细想一想，你会觉得这是一件比较神奇的事情。本来就是不同的框架，怎么能够无缝地集成在一起呢？&lt;/p&gt;
&lt;p&gt;无论从面试角度，还是日常开发角度，这都是一个值得分析和探讨的话题。因为我们自己也可能需要开发类似 Dubbo、Mybatis 这样的第三方框架，然后完成与 Spring 框架的集成。显然，这是一类偏向于实践的话题，因此，面试官也会更多地从应用的角度出发来考查候选人，常见的提问方式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你能列举 Spring 框架所提供的常见启动扩展点，以及它们的使用方式吗？&lt;/li&gt;
&lt;li&gt;Spring 中 Bean 中的 Constructor、@PostConstruct 以及 InitializingBean 这三者之间的执行顺序是怎么样的？&lt;/li&gt;
&lt;li&gt;如何基于 Spring Boot 的自动配置原理实现一个 starter 组件？&lt;/li&gt;
&lt;li&gt;你能描述 Dubbo 框架的启动过程吗？&lt;/li&gt;
&lt;li&gt;Mybatis Spring Boot Starter 的运行机制是怎么样的？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要想回答上述面试题，就需要我们掌握 Spring 框架中内置了一组功能非常强大的扩展机制。通过这些扩展机制，可以实现我们想要的集成效果。接下来，让我们对这些问题做进一步分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-20&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;目前，很多主流的开源框架都提供了自身针对 Spring 框架的系统集成模块，包括我们前面几讲介绍的 Dubbo 和 Mybatis 框架。同样的，如果我们自己实现一款开源框架，也可以利用 Spring 所提供的扩展性来完成与它的集成过程。这里，我们可以把 Spring 框架本身做一个细分，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-40-01-9b0b1f84310658c71be500249090b226-0e173.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 646px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 26.47058823529412%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAAA/0lEQVQY052Qy06DYBCFef/naMSqwTYadePOai8gQm9A7YVLa35oobQL64J8jqy7cpKTmTnnZGYyGmfidPrGMg26bw06Lw3Mgc5k/Mx8/onZbwp0+j1dPE3erSuswSW97gWBf4+mVMxum7AVpGlElq4lK4LAJApdGWKzXNooteBwKInjEVHk1locDWuEK4dQeqU8tPHIwJve4Ng6i3kL33uQC3/4b2hJMiVJZGs45GszZrMO2O1y4ULKMmNfZBS5kuv2VFUlmpI+JReuKNK6LvdZXf9p2rktx+MB12njT+8YuS28SZvVssNs5gseWS2ehL/F/TBqj21d1/8r8ld+AZVtbUZfbTFrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 40 01&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-40-01-9b0b1f84310658c71be500249090b226-0e173.png&quot; data-srcset=&quot;/static/2024-12-18-10-40-01-9b0b1f84310658c71be500249090b226-2fb9f.png 200w,
/static/2024-12-18-10-40-01-9b0b1f84310658c71be500249090b226-f1e72.png 400w,
/static/2024-12-18-10-40-01-9b0b1f84310658c71be500249090b226-0e173.png 646w&quot; data-sizes=&quot;(max-width: 646px) 100vw, 646px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从扩展性的角度讲，传统 Spring 提供了两大类扩展功能，一类是启动过程中的各种扩展点，另一类则是 Spring 内置的自定义标签体系，我们可以通过 XML Scheme 的命名空间机制来实现与 Spring 的集成。&lt;/p&gt;
&lt;p&gt;另一方面，Spring Boot 作为 Spring 框架的升级，也提供了功能强大的自动配置机制，我们也可以通过编写自定义的 starter 组件来完成与 Spring Boot 的集成。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-22&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-22&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;就目前的应用场景而言，Spring Boot 是 Java 世界中最主流的开发框架，开发自定义 starter 组件是主流的框架集成实现方案。因此，在接下来的内容中，我们重点对 Spring Boot 的自动配置机制做详细的展开。而针对传统 Spring 框架，只会简要介绍一些常见的启动扩展点机制。&lt;/p&gt;
&lt;h3 id=&quot;spring-启动扩展点&quot;&gt;&lt;a href=&quot;#spring-%E5%90%AF%E5%8A%A8%E6%89%A9%E5%B1%95%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring 启动扩展点&lt;/h3&gt;
&lt;p&gt;就技术体系而言，让我们先从传统 Spring 框架的一组启动扩展点开始讲起。事实上，在 Spring 中，启动扩展点的数量非常多，这里只挑选一组最常用的进行介绍，包括 InitializingBean、FactoryBean 以及 ApplicationListener。&lt;/p&gt;
&lt;p&gt;在 Spring 中，InitializingBean 的作用是在 Bean 的初始化时，允许开发人员添加一些定制化处理逻辑，该接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;86278293956128300000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface InitializingBean {
   void afterPropertiesSet() throws Exception;
}`, `86278293956128300000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InitializingBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterPropertiesSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们看到 InitializingBean 接口只有一个方法，即 afterPropertiesSet 方法。从命名上看，这个方法应该作用于属性被设置之后。也就是说，该方法的初始化会晚于属性的初始化。&lt;/p&gt;
&lt;p&gt;接下来要介绍的 FactoryBean 接口相信你不会陌生，它的定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78876827476126580000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface FactoryBean&lt;T&gt; {
   T getObject() throws Exception;
   Class&lt;?&gt; getObjectType();
   boolean isSingleton();
}`, `78876827476126580000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FactoryBean&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getObjectType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isSingleton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;实际上，FactoryBean 是 Spring 框架中非常核心的一个接口，负责从容器中获取具体的 Bean 对象。&lt;/p&gt;
&lt;p&gt;ApplicationEvent 和 ApplicationListener 是 Spring 框架中实现事件驱动编程的核心类。如果在一个 JavaBean 中实现了 ApplicationListener 接口，那么一旦有任何一种 ApplicationEvent 被发布，这个 Bean 将自动触发监听器处理程序。ApplicationListener 接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37715179172929190000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface ApplicationListener&lt;E extends ApplicationEvent&gt; extends EventListener {
   void onApplicationEvent(E event);
}`, `37715179172929190000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationListener&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;E&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们知道在 Spring 上下文对象 ApplicationContext 的整个生命周期中，每一个阶段或操作的开始和结束都可以触发一种事件。基于 ApplicationListener 这个监听器接口，我们就可以处理各种自定义的 ApplicationEvent。&lt;/p&gt;
&lt;h3 id=&quot;spring-boot-自动配置&quot;&gt;&lt;a href=&quot;#spring-boot-%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Boot 自动配置&lt;/h3&gt;
&lt;p&gt;Spring Boot 的自动配置功能强大，但也有一定的复杂度，让我们通过 @SpringBootApplication 注解来深入理解其背后的实现原理。&lt;/p&gt;
&lt;p&gt;通过查看 @SpringBootApplication 注解的定义，我们发现该注解实际上是一个复合注解，由 @SpringBootConfiguration、@ComponentScan 和 @EnableAutoConfiguration 所组成。我们知道 @ComponentScan 是传统 Spring 框架中就内置的注解，而 @SpringBootConfiguration 注解也很简单，实际上只是对 Spring 框架中另一个常用组件 @Configuration 的一种包装，本身没有定义任何内容。&lt;/p&gt;
&lt;p&gt;所以，我们接下来重点剖析 @EnableAutoConfiguration 注解，该注解定义如下所示。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62839210781249634000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = &amp;quot;spring.boot.enableautoconfiguration&amp;quot;;
   Class&lt;?&gt;[] exclude() default {};
   String[] excludeName() default {};
}`, `62839210781249634000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TYPE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RUNTIME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Inherited&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@AutoConfigurationPackage&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AutoConfigurationImportSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnableAutoConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; ENABLED_OVERRIDE_PROPERTY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spring.boot.enableautoconfiguration&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;excludeName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里出现了一个新的注解，即 @AutoConfigurationPackage。从命名上讲， @AutoConfigurationPackage 注解的作用就是自动对某一个代码包进行配置。&lt;/p&gt;
&lt;p&gt;另一方面，我们还看到通过 @Import 注解引入了一个 AutoConfigurationImportSelector 类。从命名上，我们也不难理解该类的作用是完成对导入的配置信息的自动选择。该类的核心方法 getCandidateConfigurations 实现了这一目标，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;71568840559871934000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected List&lt;String&gt; getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List&lt;String&gt; configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
   // ...
   return configurations;
}`, `71568840559871934000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCandidateConfigurations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AnnotationMetadata&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnnotationAttributes&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; configurations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringFactoriesLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;loadFactoryNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSpringFactoriesLoaderFactoryClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getBeanClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; configurations&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里引出了在 Spring Boot 中真正负责加载配置信息的 SpringFactoriesLoader 类。这些类之间的交互关系如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-41-06-d6867c5f348c245629811bcfee96735a-8fcec.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 645px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.13953488372093%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABbUlEQVQoz3VRa0/CUAzd//8VJJpoREBNiI/4AXmMsbGxIWMbj8EYgzD5oIADwbtjNx6SoE1O2tvT0za9XEU4Q7GQAF86i2EaKfR7dRjNPMr8Nl/mt1xdvYRh5GBZItTaBQr5BAT+HIWXBOpaEoqcBmfbNfRslaCh21HQbgvw/SGmUxd2V6bmKjUR0CM/HumUdzCZDNB4zaNlScRrVFcjronRyAKHP2z1xbBeh4f3cvl9iDcb4lcM/xnHGANjkSCkwgCOoxAkml6CLD9A13NQlMfYV6v3tG0BrivD6VcRBIu4SaQPQxYj3jAMt9sEwSfd8BmaegtZykASU6gIydjLUhpi5ZrueYWmnoVpPmGxmOFYH2+4D/bJI+5gs1lwktvXRbpj7Br+JiLzfReDgQ7PMzF0Ddq4CG9oYuRZ9G7SpzjYjT+94fGUqCA6+ngswnOzMPQMLOMG7dYdOgS9kcLHew5vvoT5PNjdj5Fmc8APDzFP/v5a6eEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 41 06&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-41-06-d6867c5f348c245629811bcfee96735a-8fcec.png&quot; data-srcset=&quot;/static/2024-12-18-10-41-06-d6867c5f348c245629811bcfee96735a-ba1e2.png 200w,
/static/2024-12-18-10-41-06-d6867c5f348c245629811bcfee96735a-72011.png 400w,
/static/2024-12-18-10-41-06-d6867c5f348c245629811bcfee96735a-8fcec.png 645w&quot; data-sizes=&quot;(max-width: 645px) 100vw, 645px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;显然，想要完成配置信息的自动选择，我们首先需要执行配置文件的加载操作，这部分功能是由 SpringFactoriesLoader 来完成的。SpringFactoriesLoader 也是 Spring Boot 自动配置得以实现的关键组件。&lt;/p&gt;
&lt;p&gt;SpringFactoriesLoader 类似 JDK 中的 SPI 机制所使用的 ServiceLoader 类，区别只是在于配置文件的存放位置和配置项对应的键值定义。在 SpringFactoriesLoader 中，我们需要通过 &lt;code class=&quot;language-text&quot;&gt;META-INF/spring.factories&lt;/code&gt; 目录来获取服务定义文件，并通过 EnableAutoConfiguration 这个健值来获取具体的配置信息。&lt;/p&gt;
&lt;p&gt;下图展示了 SpringFactoriesLoader 和 ServiceLoader 之间的区别：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-41-43-3ad4eec648c37c54103bbfbcb3117f27-72de5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 648px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 23.456790123456788%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABL0lEQVQY0z2Qx27CQBRF+f8/IYVNGrFDigVSCphi7LFDsY2JGw4tECQ2Jw8vsjg6miu9mfumMo+a9Lo3dM1r8XVpe3hP/NXC8wxWqzVJmrD/2THwhzxFBq3klbrSubHqXPVuefRfaEpmRC0qRTEhjl25wCVNPBIhy0YUi7F4ym6353A4cDweyZYZn5JPlz5eOsKNPZTMjfIxE8lOroSBgbJ1lKPj2Bq2dV9yauhPX+WxhDzP2aw3ODOF5jXQHEE10IUH75HG53NpI5SG87nHbOYShqr0iSBQRJE0TgO2262sveJ3/0v6ndL3B1jBsGTgWwwC6/9sRw6VfreG2b6g36vREZudS9of57jqSlpr8gUpi2LBZrXhzX2n/vXAXahTNS84k5mqecl5v4YeP5X5H2awXvpKxVq1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 41 43&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-41-43-3ad4eec648c37c54103bbfbcb3117f27-72de5.png&quot; data-srcset=&quot;/static/2024-12-18-10-41-43-3ad4eec648c37c54103bbfbcb3117f27-bb436.png 200w,
/static/2024-12-18-10-41-43-3ad4eec648c37c54103bbfbcb3117f27-c5634.png 400w,
/static/2024-12-18-10-41-43-3ad4eec648c37c54103bbfbcb3117f27-72de5.png 648w&quot; data-sizes=&quot;(max-width: 648px) 100vw, 648px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;SpringFactoriesLoader 基于上图中指定的配置文件名和键值获取对应的配置信息，然后基于这些配置信息来实例化配置类，Spring Boot 通过反射机制实现了这一目标。SpringFactoriesLoader 类中的 loadSpringFactories 方法展示了这一过程，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15357218411307750000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private static Map&lt;String, List&lt;String&gt;&gt; loadSpringFactories(@Nullable ClassLoader classLoader) {
   // 从缓存中获取配置内容，如果存在则直接返回
   try {
      // 基于 ClassLoader 从 META-INF/spring.factories 获取配置文件资源地址 URL
      while (urls.hasMoreElements()) {
         // 获取配置文件资源
         // 加载配置项

         for (Map.Entry&lt;?, ?&gt; entry : properties.entrySet()) {
            // 组装配置项 Key-Value
         }

         // 把配置信息放入缓存
         // 返回结果
      }
   }
}`, `15357218411307750000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadSpringFactories&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassLoader&lt;/span&gt; classLoader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 从缓存中获取配置内容，如果存在则直接返回&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 基于 ClassLoader 从 META-INF/spring.factories 获取配置文件资源地址 URL&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasMoreElements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 获取配置文件资源&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 加载配置项&lt;/span&gt;

         &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entrySet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 组装配置项 Key-Value&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// 把配置信息放入缓存&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 返回结果&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们在 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件中知道了如下所示配置项：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;79925848559834710000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\`, `79925848559834710000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到在 org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置项中定义了各种以 &lt;code class=&quot;language-text&quot;&gt;-AutoConfiguration&lt;/code&gt; 结尾的配置类。通过 SpringFactoriesLoader，Spring Boot 就能做到在服务启动的过程中把它们加载到容器中并实现自动化配置。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-20&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;p&gt;在介绍完理论知识之后，让我们回到实践。我们将分别分析 Mybatis Spring 的启动过程以及 Mybatis Spring Boot Starter 的实现原理。&lt;/p&gt;
&lt;h3 id=&quot;mybatis-spring-启动过程&quot;&gt;&lt;a href=&quot;#mybatis-spring-%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mybatis Spring 启动过程&lt;/h3&gt;
&lt;p&gt;mybatis-spring 框架完成了 Mybatis 与 Spring 之间的集成。打开 mybatis-spring 代码工程，我们快速寻找实现了 Spring 扩展点的类，这个类并不难找，它就是 SqlSessionFactoryBean 类。&lt;/p&gt;
&lt;p&gt;很典型的，SqlSessionFactoryBean 实现了 FactoryBean、InitializingBean 和 ApplicationListener 这三个扩展点接口，其定义如下所示（列举了部分重要的变量）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13213271432608820000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class SqlSessionFactoryBean implements FactoryBean&lt;SqlSessionFactory&gt;, InitializingBean, ApplicationListener&lt;ApplicationEvent&gt; {
   // ...
   private Configuration configuration;
   private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
   private SqlSessionFactory sqlSessionFactory;
   private String environment = SqlSessionFactoryBean.class.getSimpleName();
   // ...
}`, `13213271432608820000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBean&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FactoryBean&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InitializingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationListener&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt; configuration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBuilder&lt;/span&gt; sqlSessionFactoryBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt; sqlSessionFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSimpleName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们还是按照既有的思路，先来看看这些扩展点如何完成 Spring 与 Mybatis 框架之前的整合。首先，我们来看 InitializingBean 接口的实现，即如下所示的 afterPropertiesSet 方法（代码做了裁剪）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61962341577199260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public void afterPropertiesSet() throws Exception {
   // ...
   this.sqlSessionFactory = buildSqlSessionFactory();
}`, `61962341577199260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterPropertiesSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlSessionFactory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildSqlSessionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，对于 SqlSessionFactoryBean 而言，主要职责就是完成 SqlSessionFactory 的构建，这也是这个类的类名由来，而完成这个操作的最合适的阶段就是 Bean 生命周期中的 InitializingBean 阶段。我们来看一下这里的 buildSqlSessionFactory 方法的具体实现过程，这个方法非常长，但代码结构比较简单。我们抛开大量的代码细节，使用如下所示的代码来展示这个方法的结构：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2455609578187401000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
   Configuration configuration;

   XMLConfigBuilder xmlConfigBuilder = null;
   if (this.configuration != null) {
      // 如果当前的 configuration 不为空，则直接使用该对象
      configuration = this.configuration;
      // ...
   } else if (this.configLocation != null) {
      // 如果配置文件地址 configLocation 不为空，则通过 XMLConfigBuilder 进行解析并创建 configuration
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
   } else {
      // 如果以上两种情况都不满足，则创建一个新的 configuration 对象并进行参数赋值
      configuration = new Configuration();
      // ...
   }

   // 设置 objectFactory 等各种 Mybatis 运行时所需的配置信息
   // ...

   // 基于 configuration 通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory
   return this.sqlSessionFactoryBuilder.build(configuration);
}`, `2455609578187401000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildSqlSessionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt; configuration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;XMLConfigBuilder&lt;/span&gt; xmlConfigBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configuration &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果当前的 configuration 不为空，则直接使用该对象&lt;/span&gt;
      configuration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configuration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configLocation &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果配置文件地址 configLocation 不为空，则通过 XMLConfigBuilder 进行解析并创建 configuration&lt;/span&gt;
      xmlConfigBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLConfigBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configLocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configurationProperties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      configuration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlConfigBuilder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 如果以上两种情况都不满足，则创建一个新的 configuration 对象并进行参数赋值&lt;/span&gt;
      configuration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 设置 objectFactory 等各种 Mybatis 运行时所需的配置信息&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 基于 configuration 通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlSessionFactoryBuilder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;configuration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关于 SqlSessionFactoryBuilder，我们知道该类会创建一个 DefaultSqlSessionFactory，可以通过 DefaultSqlSessionFactory 进而获取 SqlSession 对象的实例。&lt;/p&gt;
&lt;p&gt;然后，我们来关注 SqlSessionFactoryBean 实现的 FactoryBean 接口，从接口的泛型定义上，我们明白它的 getObject 方法应该返回的是一个 SqlSessionFactory 对象，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;83708340679334350000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public SqlSessionFactory getObject() throws Exception {
   if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
   }

   return this.sqlSessionFactory;
}`, `83708340679334350000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlSessionFactory &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;afterPropertiesSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlSessionFactory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这里的实现过程非常简单，如果目标 sqlSessionFactory 还没有被创建，就直接调用了前面介绍的 afterPropertiesSet 方法完成该对象的创建并返回。&lt;/p&gt;
&lt;p&gt;最后，我们需要关注的是 onApplicationEvent 方法，这是 ApplicationListener 接口的具体实现，用来对 Spring 中所生成的 ApplicationEvent 进行响应，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;98897352038655820000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Override
public void onApplicationEvent(ApplicationEvent event) {
   if (failFast &amp;&amp; event instanceof ContextRefreshedEvent) {
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
   }
}`, `98897352038655820000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onApplicationEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ApplicationEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failFast &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContextRefreshedEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlSessionFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMappedStatementNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;就场景而言，这个方法的作用是在接收到代表容器刷新的 ContextRefreshedEvent 事件时，重新获取各种 MappedStatement。这里通过调用 Configuration 的 getMappedStatementNames 方法完成这一操作，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;85124639296091850000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public Collection&lt;MappedStatement&gt; getMappedStatements() {
   buildAllStatements();
   return mappedStatements.values();
}`, `85124639296091850000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MappedStatement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMappedStatements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;buildAllStatements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; mappedStatements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再往下看，就到了 Mybatis 内部的实现细节了，我们不再展开。至此，SqlSessionFactoryBean 中与 Spring 框架集成的相关内容就介绍到这里。通过 SqlSessionFactoryBean，我们就可以获取 SqlSessionFactory 对象，这是 Mybatis 框架启动过程的主要目标。&lt;/p&gt;
&lt;h3 id=&quot;mybatis-spring-boot-starter&quot;&gt;&lt;a href=&quot;#mybatis-spring-boot-starter&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mybatis Spring Boot Starter&lt;/h3&gt;
&lt;p&gt;基于前面介绍的 Spring Boot 中应用程序的自动配置机制，我们来做一些实践，通过剖析 Mybatis Spring Boot Starter 的启动过程来加深对所介绍内容的理解。&lt;/p&gt;
&lt;p&gt;在 mybatis-spring-boot-starter 中存在几个代码工程，我们重点关注 mybatis-spring-boot-autoconfigure 工程。而在这个代码工程中，最重要的显然就是 MybatisAutoConfiguration 类。对于 Spring Boot 中的 &lt;code class=&quot;language-text&quot;&gt;-AutoConfiguration&lt;/code&gt; 类，我们首先需要重点关注的是类定义上的注解，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1785482929576831500&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
   // ...
}`, `1785482929576831500`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@org&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnSingleCandidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MybatisProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@AutoConfigureAfter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DataSourceAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MybatisAutoConfiguration&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InitializingBean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们看到这里用到了 @ConditionalOnClass 和 @ConditionalOnSingleCandidate 这两个注解，它们就是 Spring Boot 中的条件注解。在介绍 MybatisAutoConfiguration 之前，有必要对这些注解做一定展开。&lt;/p&gt;
&lt;p&gt;我们在前面的介绍中已经了解到以 &lt;code class=&quot;language-text&quot;&gt;-AutoConfiguration&lt;/code&gt; 结尾的自动配置类数量会很多，在一个应用程序的开发过程中，我们通常不会全部使用到。这时候就需要引入一种机制来对这些自动配置类进行过滤。为此，Spring Boot 提供了一组 @ConditionalOn 系列条件注解。通这些注解，我们就可以基于特定的条件来选择性地加载某些配置类。&lt;/p&gt;
&lt;p&gt;在 Spring Boot 中常见的条件注解可以参考下图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-43-05-9d476027742985f73efaf1656be6c900-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 39.876352395672335%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABuElEQVQoz02R6XLaQBCE9f4PkpRdRYxxoIwdW+he3RKYQ5xGAiGD3+HLIFc5+dHVszs7PT072nplkWd9lNclCO4Jgx5ZOmC1euV42KA2Ic9bncHbkF7ap5v8bvmK/mTIQzbgLurxtHxF35lop9OYqoyYzyyWhdPy/j3kfM5pTiVv9ZyoSVG7AF/eqfcAtQ9wNwpv5+PvQ6yl097nlwnaTh4uC5MkHpImT8TRkGJhiqjHhwim5RhjY/Ey03mZj/6D/o3H/Jk/kndLH60+ZpQSLOZGK7QQVGVA0yR8NCWTeoaqQ5x3hbVxGBWWxB721sOTuivrUquqkPicoZVlImOaTKfGNxcLm7qOOX9UTI4z4kuGkib22m2hz02MpY2zU1+xNEkvY+JGBHdbX4R0wajl2VQXtyNxrVrBrJpgly7W1pHRbcyrS1mksf4HvTAwJa+OIdqhysWRw2rpyV+6LRfCzSnn81IzOxX4VSSfHxEdUkGCL0u7nt2NT1jGOCsPd60Yf07RMlm/afzAsW+xrRvM0TW+EacDqv0KQ5p1kjs6UZefzi03qkM3feAu6fErvqcTd79yUj+YD/kLj2Mhkm7Th1kAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 43 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-43-05-9d476027742985f73efaf1656be6c900-a40ca.png&quot; data-srcset=&quot;/static/2024-12-18-10-43-05-9d476027742985f73efaf1656be6c900-3426a.png 200w,
/static/2024-12-18-10-43-05-9d476027742985f73efaf1656be6c900-f3a4f.png 400w,
/static/2024-12-18-10-43-05-9d476027742985f73efaf1656be6c900-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在前面介绍的 MybatisAutoConfiguration 类上，我们发现了 @ConditionalOnClass 和 @ConditionalOnSingleCandidate 这两个条件注解。基于这两个条件注解，我们可以明确 MybatisAutoConfiguration 能够实例化的前提有两个，一个是类路径中存在 SqlSessionFactory 和 SqlSessionFactoryBean，另一个是容器中只存在一个 DataSource 实例。两者缺一不可，这是一种常用的自动配置控制技巧。&lt;/p&gt;
&lt;p&gt;然后，我们在 MybatisAutoConfiguration 类上看到了一个 @EnableConfigurationProperties 注解。通过这个注解，所有添加了 @ConfigurationProperties 注解的配置类就会自动生效。这里的 @EnableConfigurationProperties 注解中指定的是 MybatisProperties 类，该类定义了 Mybatis 运行时所需要的各种配置信息，而我们在 MybatisProperties 类上确实也发现了 @ConfigurationProperties 注解，并指定了 prefix 为 &lt;code class=&quot;language-text&quot;&gt;mybatis&lt;/code&gt;，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;95474102994395920000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@ConfigurationProperties(
   prefix = &amp;quot;mybatis&amp;quot;
)
public class MybatisProperties {
   // ...
}`, `95474102994395920000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mybatis&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MybatisProperties&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后，在 MybatisAutoConfiguration 类上还存在一个 @AutoConfigureAfter 注解，这个注解可以根据字面意思进行理解，即在完成某一个类的自动配置之后再执行当前类的自动配置，这个需要提前装配的类指的就是 DataSourceAutoConfiguration。&lt;/p&gt;
&lt;p&gt;理解了 @ConditionalOnClass、@EnableConfigurationProperties 和 @AutoConfigureAfter 等一系列注解之后，我们回过头来再看 MybatisAutoConfiguration 类的代码结构就显得比较简单明了。MybatisAutoConfiguration 类中核心方法之一就是如下所示的 sqlSessionFactory 方法：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62475078670196910000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
   SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
   factory.setDataSource(dataSource);
   factory.setVfs(SpringBootVFS.class);
   if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
   }
   applyConfiguration(factory);

   // 省略一系列配置项设置方法
   return factory.getObject();
}`, `62475078670196910000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactory&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqlSessionFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DataSource&lt;/span&gt; dataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBean&lt;/span&gt; factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlSessionFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDataSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setVfs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpringBootVFS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConfigLocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConfigLocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resourceLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConfigLocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;applyConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;factory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 省略一系列配置项设置方法&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，这里基于前面介绍的 SqlSessionFactoryBean 构建了 SqlSessionFactory 实例。注意到在该方法上同样添加了一个 @ConditionalOnMissingBean 注解，标明只有在当前上下文中不存在 SqlSessionFactoryBean 对象时才会执行上述方法。&lt;/p&gt;
&lt;p&gt;接下来，我们需要在 &lt;code class=&quot;language-text&quot;&gt;META-INF/spring.factories&lt;/code&gt; 文件中明确指定自动配置类。根据 Spring Boot 自动配置机制的原理，对于 mybatis-spring-boot-autoconfigure 工程而言，这个配置项内容应该是这样：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;97384908600741480000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration`, `97384908600741480000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;至此，整个 Mybatis Spring Boot Starter 的介绍就告一段落。作为总结，我们可以把创建一个 Spring Boot Starter 的过程抽象三个步骤，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-18-10-43-47-a24e8f86720a24c38e6df3e807da0e20-72de5.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 648px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 25.925925925925924%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABN0lEQVQY0z2Q207CUBBF+///oUEiYLkYRKwBEVqUe2mRUlp6CtILfIFmOemDDzszk9mTrNma6zxxVAbzWQ1zVMJe1WV+wdv2yJOcrvNKO+7SiQyqdp0bq8TtuMzdtFLUstSO3LejLkbQQ0uSA3l+II49XHdKmoSkaUCWHfn9+eV0+SaQfXRV7JMA77Rjd/Lxz/v//pBHhOJRuULb+32CvcFq2WIxq7OYN4lVjzC0uGRX+psB1UWd6kRHF4++aPIguv+ooa9aNJ12odqywVs4REvTUAgDjscvPG/G+bwjS30hVAVhlCpWoY0drnGUiys+N96wPjhFvxE5aoMdrQtqbfLZkOzKWGaF90GJsVWRPB8kwwFXIRzuRrxfTEb5mGffQJcPGstH2ttnGvYjLaEbphaDzMQ8jfkDuG5Z7r+85LgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 18 10 43 47&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-18-10-43-47-a24e8f86720a24c38e6df3e807da0e20-72de5.png&quot; data-srcset=&quot;/static/2024-12-18-10-43-47-a24e8f86720a24c38e6df3e807da0e20-bb436.png 200w,
/static/2024-12-18-10-43-47-a24e8f86720a24c38e6df3e807da0e20-c5634.png 400w,
/static/2024-12-18-10-43-47-a24e8f86720a24c38e6df3e807da0e20-72de5.png 648w&quot; data-sizes=&quot;(max-width: 648px) 100vw, 648px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在日常开发过程中，基于上述三个步骤，我们就可以参考 Mybatis Spring Boot Starter 的实现方式来设计并开发自定义的 starter 组件。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-22&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-22&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;正如在问题背景部分中给出的常见面试题所示，面试官在面试过程中往往不会直接问 &lt;code class=&quot;language-text&quot;&gt;Mybatis 与 Spring 框架是如何集成&lt;/code&gt; 这样的问题，而是会考查具体的知识点，例如抛出 &lt;code class=&quot;language-text&quot;&gt;Spring 中 Bean 中的 Constructor、@PostConstruct 以及 InitializingBean 这三者之间的执行顺序是怎么样的？&lt;/code&gt; 类似的问题。这是一类比较好的面试题，我作为面试官也经常会用这种类型的题目考查面试者对于 Spring 中一些基本实现机制的理解程度。&lt;/p&gt;
&lt;p&gt;从考点上讲，一方面这道题的知识点比较丰富，涉及到对 Spring 框架中的一些核心概念；另一方面，这道题也具有一定的综合性，要求面试者在理解这些核心概念的基础上能够用自己的表达方式串接起来。&lt;/p&gt;
&lt;p&gt;针对这一问题，我们首先应该对 Spring 中 @PostConstruct 注解和 InitializingBean 接口的作用和应用场景有明确的认识，最好有一定的实践基础，在回答这个问题时先说明这一点。然后，在介绍了应用方式的基础上，我们需要深入这些注解和方法的背后，进一步介绍 BeanPostProcessor 等 Spring 内部的实现接口及其实现过程。整个回答过程中涉及的概念和对象可能有点多，注意侧重点和回答问题的节奏。有些地方不用说得过细，只需要围绕“执行顺序”这一核心考点进行展开即可，避免陷入细节而导致问题回答思路上的混乱。&lt;/p&gt;
&lt;p&gt;随着 Spring Boot 成为日常开发的主流框架，Spring Boot 的自动配置机制也变成了一个经典问题。但凡是使用过 Spring Boot 的候选人，我一般都会通过这个问题来考查对方的知识体系。自动配置是 Spring Boot 最核心的机制，该问题的考点明确，问题的答案也很明确，大家通过记忆以及加上自己的一些理解进行解答即可。&lt;/p&gt;
&lt;p&gt;从回答思路上，Spring Boot 的自动配置机制涉及的知识点包括两大部分内容。首先我们需要介绍 @SpringBootApplication 注解及其背后的三大注解 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan，这几个注解必须点到。然后，随着对这些注解的深入阐述，我们需要再引出具有与 SPI 实现机制类似功能的 SpringFactoriesLoader 类，该类包含了自动配置相关的一些配置项处理方式。本讲详细阐述了 Spring Boot 自动配置机制的实现原理，从源码角度分析了为什么 Spring Boot 能够做到自动配置。文中所述的知识体系都可以用来回答这个问题。由于内容比较多，回答过程中还是建议需要把握节奏，避免过多嵌入细节。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-20&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;在本讲内容中，我们首先介绍的是 Spring 框架中的几个核心扩展点，这些扩展点是 Dubbo 和 Mybatis 等开源框架与 Spring 进行集成的主要入口。然后，Spring Boot 的自动配置原理也是我们需要掌握的内容，因为它们被广泛地应用到与 Spring Boot 框架的集成过程中。围绕前面介绍的这些内容，我们基于 Mybatis 框架讲解了 Mybatis Spring、Mybatis Spring Boot Starter 等专用于两者之间集成的框架实现原理。&lt;/p&gt;
&lt;p&gt;很多时候我们需要对框架的功能进行完善和升级，这时候就需要框架本身具备高度的可扩展性。针对这一点，业界也存在一些主流的架构模式可以用来实现这一目标，其中最具代表性的就是微内核架构。下一讲我们就将结合 Dubbo 等具体框架围绕“为什么很多开源框架都会内置一套微内核架构？”这一话题展开详细的分析。&lt;/p&gt;
&lt;h1 id=&quot;组件扩展：为什么很多开源框架都会内置一套微内核架构？&quot;&gt;&lt;a href=&quot;#%E7%BB%84%E4%BB%B6%E6%89%A9%E5%B1%95%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BE%88%E5%A4%9A%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6%E9%83%BD%E4%BC%9A%E5%86%85%E7%BD%AE%E4%B8%80%E5%A5%97%E5%BE%AE%E5%86%85%E6%A0%B8%E6%9E%B6%E6%9E%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;组件扩展：为什么很多开源框架都会内置一套微内核架构？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们讨论了框架与框架之间如何进行集成的实现过程，也引出了框架扩展性这一话题。&lt;/p&gt;
&lt;p&gt;对于软件开发而言，扩展性是一个永恒的话题，实现系统可扩展的方法有很多。而本讲要介绍的微内核架构模式就是其中一种非常具有代表性的架构模式，除了我们熟悉的 Dubbo，还有 SkyWalking、ShardingSphere 等一系列主流的开源框架都应用了这一架构模式。&lt;/p&gt;
&lt;p&gt;那么，什么是微内核架构？为什么很多开源框架都会内置一套微内核架构？作为非常经典的一类面试题，本讲内容将和你一起分析和探讨这一组件背后的设计理念以及在开源框架中的具体实现方式。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-20&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-20&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在日常开发过程中，你应该经常会遇到这样的需求：针对某个业务场景，我们希望在系统中添加一种新的处理逻辑，但又不想对现有的系统造成影响。从架构设计上讲，这是一种典型的系统扩展性需求。&lt;/p&gt;
&lt;p&gt;从扩展性的实现策略上讲，插件式系统是我们追求的一个目标。我们希望打造如下图所示的效果，调用者能够通过基于配置的插件机制动态获取它想要的任何插件。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-49-20-d758229412d548cc1b62797913c00e7d-8fcec.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 645px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 54.10852713178295%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABe0lEQVQoz32R2W6CYBCFef8n6Y2JaUyb9A3KjdTUsiskimyCQdkUOP1nLAZs9E+OIZ6ZbzZJmU+hazMsvqYw9Fd4no3+7XYr/CynUOYTWOYbTOMDaZL+uR3KssLy+13kTiB/vkBTZ5AC30IUWgh8A/v9Cnme3YBFkfF/YWAhjm0kexeXy+Xmd0Jx7HC+52niewWpN8qyRteBq9q2zSqKQgA6EewPihQ4Ho84nU6ieI62Batpr77UCcr5fBbj7TiYlGUZqyhyTtI0DUEQ4HA4YL1eQ1VVLBYLVpIkME0Dm83mCqSfpmk4+NGjbmRZZjgVJAg1QBDy0jSF7/tjIAVRt/eiF0URwjDkSahwVVWjgmVZjoG0aBpx+IZA8nRdh+u6LOqoj+nzqfsbkJ5pmjwGjbDdbuE4DgzD4APQ/hRF4U56yLBgK67SX1/qTcuyOJlMGo1Eo5FHwDiO/4GGwLquxyM/OwrBaIdDyH2HIyAdhfby6Ci0Bur4GbD3fwG1qE3Q2uskTAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 49 20&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-49-20-d758229412d548cc1b62797913c00e7d-8fcec.png&quot; data-srcset=&quot;/static/2024-12-31-10-49-20-d758229412d548cc1b62797913c00e7d-ba1e2.png 200w,
/static/2024-12-31-10-49-20-d758229412d548cc1b62797913c00e7d-72011.png 400w,
/static/2024-12-31-10-49-20-d758229412d548cc1b62797913c00e7d-8fcec.png 645w&quot; data-sizes=&quot;(max-width: 645px) 100vw, 645px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&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;Dubbo 框架采用了什么技术实现方式来确保它具有高度的可扩展性？&lt;/li&gt;
&lt;li&gt;Dubbo 框架为开发人员提供了哪些扩展点？&lt;/li&gt;
&lt;li&gt;如果想要在 Dubbo 中实现一个自定义的扩展点，你有什么办法？&lt;/li&gt;
&lt;li&gt;SPI 是什么概念？我们如何使用 SPI 机制？&lt;/li&gt;
&lt;li&gt;Dubbo 中的 SPI 机制与 JDK 中的 SPI 机制有什么区别？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于上述问题，我们需要引出本讲要讨论的主题，即微内核架构。这是一种应用非常广泛的架构模式，也是面试过程中出现频率非常高的一个技术组件。&lt;/p&gt;
&lt;p&gt;接下来，我们来对这类问题做一些分析和展开。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-21&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;微内核架构有时候就直接被称为插件架构，它在组成结构上包含两部分组件，即内核系统和插件。&lt;/p&gt;
&lt;p&gt;这里的内核系统用来定义插件的实现规范，并管理着插件的生命周期；而各个插件是相互独立的组件，各自根据实现规范完成某项业务功能，并嵌入到内核系统中，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-54-09-2f000df5d9da64bdf9aeecdaf861a21b-28558.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 514px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 62.64591439688716%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAACkUlEQVQ4y22TCVPaUBCA8///hz1sBUVQA+UGiygKciSUJAgIIRQFFFKwwtdNcDpVuzM7e7y93h7K/HHBfL5FDzabjY8eNBpFritBSqUglXIAXSv5+vV6/df2UfwXC1fonOXqCaWpn6A1jgWjOI794vCMF1PX8gz6x5SvdnGGUTSt4L8/P//2aa9nie6YWjWC0YpxVgijtH4ExTFArxvFNFvbzGwrnD1MMYwG9foVVrvpy7xYeKDrFbqdCKXzHUbDGIXTLyimqdEWY8PQGQ6HjMdjXPn+cv6LJ/cJ9+EX9UrD51eLleiXzOV747sx/X5f/Lb+Xpzb2y4Kb2BwOyCcCpOw0iStDMl2hkDpgPRNlpTIcTNFJH0kFTn8DxRNy0kf8lSrWR4eZr5SrcX4zgUFLgkPVE5GcUK9IwqbEnmKJPS0b3d391P8MvL1U5lBVvgzlF4nhGUEGdpRLi/P6VgdPqV3yS3POOpF2TfC7OkH7DVDqHac3OqMz+mvtI2OTD8vwzqmWvnI9D7Oaf4DSvkqISuRpF7LM5lO/MwxLYE6TRAwD4lNUpw4cb/SSD+KOkuQbmVlFZB+j6hUUpQu4tRqGZl28X0PHXtIMLFPrJ0k2c0SMyVw+YBvntzJELUShJKH3P0c/7+Hg34be9Dxd8pxHB+9KXpTXrkr3EeX66uq8E8sF0sfZ9MZzmiEbdu+n213ZMKWbMkAxTJCtH4cYJkRTKP1KtvCXdDttWm1tivhuu6rd00rc2OFpYdfpCiVfO4zSq16SPX6UPYp7mf491K0xne5FJVGfZ9+T5UDKL67lGZT5by4R9v6JvQI5f5+wmQyxaNvb9kLULsOkMvuCB+iqV+8uuX1eiOrM2E2mwm992/6D1DDsCAuAofjAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 54 09&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-54-09-2f000df5d9da64bdf9aeecdaf861a21b-28558.png&quot; data-srcset=&quot;/static/2024-12-31-10-54-09-2f000df5d9da64bdf9aeecdaf861a21b-843f4.png 200w,
/static/2024-12-31-10-54-09-2f000df5d9da64bdf9aeecdaf861a21b-8216e.png 400w,
/static/2024-12-31-10-54-09-2f000df5d9da64bdf9aeecdaf861a21b-28558.png 514w&quot; data-sizes=&quot;(max-width: 514px) 100vw, 514px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;基于微内核架构，当系统中的某个组件需要进行修改时，要做的只是创建一个新的组件并替换旧组件，而不需要改变原有组件的实现方式，更加不需要调整整个系统架构。&lt;/p&gt;
&lt;p&gt;那么这里的插件具体指的是什么呢？这就需要我们引入一个新的概念，即 SPI，英文全称叫 Service Provider Interface，也就是服务提供接口。可以认为 SPI 就是应对系统扩展性的一个个扩展点，也是我们对系统中所应具备的扩展性的抽象。&lt;/p&gt;
&lt;p&gt;插件化实现机制说起来简单，做起来却不容易，我们需要考虑两方面内容。一方面，我们需要梳理系统的变化并把它们抽象成一个个 SPI 扩展点。另一方面，当我们实现了这些 SPI 扩展点之后，就需要构建一个能够支持这种可插拔机制的具体实现，从而提供一种 SPI 运行时环境。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-54-58-e8e5ea6dbadfd6da8ae4a232af954e5c-bf3e7.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 504px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 71.03174603174602%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAC6klEQVQ4y12UW3PaSBCF+f8/IQ+7L0kqW2UXjkNMDMHiJsDGXGwQoAsYgwCBlcUgbosRXxoRu1w7qlaXembOnJ4+rdB+D/vD6904fL/GlssFD50qnXYlsNVqIbakUomQSX+iWgkzHHb/7PMJiQs+Xl72bLc+u90R1Pf9wI9GfUw9TP3+H4zWKU+TPp7n0e9d0n04ZzKO0e+33gOC6w7QtCuaTUXYFFmv129sp1NXYlcYepS2Fef5+SmIt9tlTONaQMtywPQIKE8AOLA1et2vdKywnBxjMpmw9/fMZjPmwubJdRk5DqZpCaDE5nMeH23GY1cOnMu6ucSf8XdvDEdYZlZYpGRTDnfikqqnCTfOiRo/iHfiXIr9EEs6GZRRmmg7RsbNkZpkSU9Ufg4UzKFFyLbbkpJGt9uSk6Z/qgLR5k9Oqp/lGs64r52gGeckZhliszRx8ap/S4l7ilQDn91dU7cbhHT9O63GCXY/gq4X+PVryuzfZ5S2MGyFieQ+ogioYn5DkUJECh/5XvhEyo5xva9w45e5FdDMpoA2aBKq1y5oamc0tMP9aW/FSOgKibkweSkJi3sysjlvRagWP1MpfkGV9dldiZzM58Qn1/kjoOfNWCxmwmwcpLzZbIThjHgz8ZZOcX/wd+Q3N6iLPDkxdVMk/xCl1Dijbl1wZX2j4RjHomy3O5FAhnLpVJie0zGqKJ0siVWa5EpFWWVRlllhoZLc5EhvC6TEzvJ/c5H8QCL7FxHtC3eilgDQ85aBxkzjVER8IhIq0xn1KPcrsqhGbaihOS3qTlN8k1uZzzRUUYJKzigSK6Wo2HWmkm0AuBP9DKXk/V6Nx+5dIKP3YzX3GPY6OIMu7thh7a1fG0w2+3TbLQa9NmPHJvT/Pn4dh9bb47P5byciLoo+w5KFXEcnzfZlF2hLtM9g2EKXljSNr+IjR4bHn4EACEgA9O7ncOhv276j93gpnXQhfXsjfe/zSnE0soI5Q8D0VpTfkAb396U5fvQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 54 58&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-54-58-e8e5ea6dbadfd6da8ae4a232af954e5c-bf3e7.png&quot; data-srcset=&quot;/static/2024-12-31-10-54-58-e8e5ea6dbadfd6da8ae4a232af954e5c-3e0a5.png 200w,
/static/2024-12-31-10-54-58-e8e5ea6dbadfd6da8ae4a232af954e5c-4bca5.png 400w,
/static/2024-12-31-10-54-58-e8e5ea6dbadfd6da8ae4a232af954e5c-bf3e7.png 504w&quot; data-sizes=&quot;(max-width: 504px) 100vw, 504px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们就来一起讨论与微内核架构相关的具体技术体系。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-23&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-23&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;微内核架构在 Dubbo 等主流开源框架中应用非常广泛。这些框架采用的实现方式也都比较类似，基本思路就是引入前面提到的 SPI 机制。&lt;/p&gt;
&lt;p&gt;那么，怎么实现 SPI 机制呢？事实上，JDK 已经内置了一套实现 SPI 机制的开发组件。基于这套组件，想要实现 SPI，具体来说要完成三个步骤，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-55-36-57ea14b6d16cbff7669a872381890173-87f29.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 602px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 74.25249169435216%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsSAAALEgHS3X78AAAB6klEQVQ4y5WUbW+iQBSF/f9/ZdMWdF2tb13bftustb6AIKKAMqDCUFrNmkjPXqBqNbDNkpwwBOa5M+fcodB/rmCq1zDRapClMsZqFeasReMi5nMJ8fX+HpGSEb66CqpShrduw3VaBC0ld+7dQ5+IcJzhJ+DXsAQoS98xt2oYK0VYZg0ua8GxSaxOOgdeKn13XqjQfxYwGYvQVBGB3wYnvfAHrJZ12Iv+EZhu96RcIOcOgoAl4txOFHBG/mnwPO9j0klRdHIyy4ZCnhdvbyF6vTsK7BdMswPD6FCRLukJptHFbvcne4WpH9FRUbRPXqxXUwpJgDy8hq4VoY4ESMMrKlAibxtUMMgHJp4cvYlS4Nqg9qlgNq2Sl01K/hHS4AYzvQLLaGC7CbOBl1s9AJeuTmFdU/IVsMVtIk0VqAuoE5wGwtDP7IAMYFoxnmBZXTA2ALMHsO1+0kam+URFerl9mRnK4eM40f3+pPh5t9sT1MBqxbDdhtiQl5vNC15fOS2C56/w4C0uvA2CJUZykcK6gaqUKCgBnd/fyIaftJO7fwPPdQC6GPSuoMgilJFIZ16g8KowKMDFvJ7fh3lh+b6TTGaLBp33Mvz1PXwvVpuObPN/gOnKOV/SVn9QP94i/rG4rE1+PhDwkcJq4i9UdXYvQdNTeAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 55 36&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-55-36-57ea14b6d16cbff7669a872381890173-87f29.png&quot; data-srcset=&quot;/static/2024-12-31-10-55-36-57ea14b6d16cbff7669a872381890173-38577.png 200w,
/static/2024-12-31-10-55-36-57ea14b6d16cbff7669a872381890173-26124.png 400w,
/static/2024-12-31-10-55-36-57ea14b6d16cbff7669a872381890173-87f29.png 602w&quot; data-sizes=&quot;(max-width: 602px) 100vw, 602px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;对于 SPI 而言，我们需要设计一个服务接口，并根据业务场景提供不同的实现类。然后，我们在 Java 代码工程的 META-INF/services 目录中创建一个以服务接口命名的文件，并配置对应的想要使用的实现类。在代码工程中执行这些步骤，最终我们可以得到一个包含 SPI 类和配置的 JAR 包。&lt;/p&gt;
&lt;p&gt;而对于 SPI 的使用者，就可以通过 JAR 包中 META-INF/services/ 目录下的配置文件找到具体的实现类名并进行实例化。&lt;/p&gt;
&lt;p&gt;上图中的后面两个步骤实际上都是为了遵循 JDK 中 SPI 的实现机制而进行的配置工作。&lt;/p&gt;
&lt;p&gt;接下来，我们可以通过简单的代码示例来演示这些步骤。&lt;/p&gt;
&lt;p&gt;让我们来模拟这样一个应用场景，一般业务系统中都会涉及日志组件，我们希望对业界主流的日志工具做一层包装，以便支持日志组件的灵活应用。那么，基于 SPI 的约定，我们将创建一个单独的工程 log-spi 来存放服务接口，并给出接口定义，请注意这个服务接口的完整类路径为 com.spi.LogProvider，接口中只包含一个以 Info 级别记录日志信息的简单方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27011643828351373000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`package com.spi;

public interface LogProvider {
   // 记录 Info 日志
   public void info(String info);
}`, `27011643828351373000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 记录 Info 日志&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; info&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;假设系统在设计之初使用的是 log4j 日志库。然后我们需要实现这个服务接口，这里创建另一个代码工程 log-log4j 用来提供基于 log4j 日志库的实现，请注意，这个实现类的名称是 Log4jProvider，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31677185936169284000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class Log4jProvider implements LogProvider {
   @Override
   public void info(String info) {
      System.out.println(&amp;quot;Log4j:&amp;quot; + info);
   }
}`, `31677185936169284000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Log4jProvider&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; info&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Log4j:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; info&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来的这个步骤很关键，我们需要创建一个以 LogProvider 这个接口对应的完整类路径命名的文件，并放在 META-INF/services/ 目录下。在这个文件中，我们指定该接口的实现类为前面已经创建的 Log4jProvider，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-56-47-2ab82cc4f0804544cee087434a7365bf-d80b6.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 492px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.68292682926829%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABcklEQVQoz42S626jMBSEef/HWm2lXW3VKlLLJdjEQDENEAIJNwNmemy1/ZGNVnukkW0sPo/n2MmyNyRJCikljQlknqNtW1xI5emMTMaoComyrFBVFd4yCc64FeMcYRgi2Ac4HASacwOHixQhY3C9V4JJaK2tZqWg1Ixpmux8HEccj0dcrhf4PMGzy7DzGP7sPDs+0TqvLnCu7QlZwhDQSSyKEEUcp/qE2zJg40avKxa9YZpXKNIw0cGLtut51XC2bUPXlEhjDp+sx7GA3jQhNgsy+6b6vofruliWBf8qCzRXbJuacuDwfA8ijsE5A2MhjkXxDfQ87xto/rsnZ+waFLlASAG/kAM/8G3YpkHDMH76BM2H/wNqykP1DdLDnhxGCAMXY9/9dZV7wHtgx3xcKUzV1dgLgUfqGMtKvLcj5LlHVncorwrdTYZfgLsZfm2IesBvluPnq8CDG+PHS0QS+LVPqZszCsrzFjjPs31SRoqe1wcANa2RgHXXoQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 56 47&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-56-47-2ab82cc4f0804544cee087434a7365bf-d80b6.png&quot; data-srcset=&quot;/static/2024-12-31-10-56-47-2ab82cc4f0804544cee087434a7365bf-3e95a.png 200w,
/static/2024-12-31-10-56-47-2ab82cc4f0804544cee087434a7365bf-11c87.png 400w,
/static/2024-12-31-10-56-47-2ab82cc4f0804544cee087434a7365bf-d80b6.png 492w&quot; data-sizes=&quot;(max-width: 492px) 100vw, 492px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;最后，我们创建一个外部工程来调用服务接口，首先需要将 log-log4j 所生成的 JAR 包添加到这个外部工程中的类路径中。然后，我们使用 JDK 中 ServiceLoader 工具类来完成对 LogProvider 实例的加载。在这里，我们通过该工具类的 load 方法获取所有的 LogProvider 实例，然后遍历这些实例并调用服务接口方法，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28996838753269105000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import java.util.ServiceLoader;
import com.spi.LogProvider;

public class Main {
   public static void main(String[] args) {
      ServiceLoader&lt;LogProvider&gt; loader = ServiceLoader.load(LogProvider.class);

      for (LogProvider provider: loader) {
         System.out.println(provider.getClass());
         provider.info(&amp;quot;testInfo&amp;quot;);
      }
   }
}`, `28996838753269105000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServiceLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;spi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;ServiceLoader&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; loader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServiceLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LogProvider&lt;/span&gt; provider&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; loader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;provider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         provider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;testInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;运行这段代码，我们会得到系统的输出。可以看到这里获取的是针对 log4j 的 Log4jProvider 类中的具体方法实现，表示整个 SPI 实例的加载过程是正常的，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84453089329341730000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class com.spi.Log4jProvider
Log4j:testInfo`, `84453089329341730000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class com.spi.Log4jProvider
Log4j:testInfo&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;请注意，在上面这个 main 函数中，我们并没有引入任何与 Log4jProvider 相关的包结构，但在运行过程中，却实现了对 Log4jProvider 类的动态调用，这是微内核架构的核心特征，对组件之间的依赖关系进行了解耦，从而确保了系统的扩展性。&lt;/p&gt;
&lt;p&gt;现在，让我们来考虑这样一种场景。随着工具的更新或者架构的调整，我们需要提供一套基于 logback 日志库的日志实现来替换现有的基于 log4j 的方案。基于扩展性考虑，最好的办法是提供另一个 SPI 实例。这样，我们创建新的一个代码工程 log-logback，并完成与 log-log4j 代码工程一样的开发工作。然后，我们把 log-logback 所生成的 JAR 包添加到外部工程的类路径中并去除原有 log-log4j 的 JAR 包，完成这些操作之后，我们再来执行前面的 main 函数，得到的就是来自 LogbackProvider 类中的输出结果，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;93556256867589420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class com.spi.LogbackProvider
Logback:testInfo`, `93556256867589420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;class com.spi.LogbackProvider
Logback:testInfo&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当然，你可以根据需要提供任何 LogProvider 接口的实现类并动态集成到系统的执行流程中。请注意，无论是添加、替换或者移除具体的 SPI 实现，对于原有的外部工程而言，我们并没有做任何的代码调整。这就满足了我们在本讲开头提到的扩展性的设计理念，即在现有系统中添加新的组件时，不会对现有的系统造成影响。&lt;/p&gt;
&lt;p&gt;至此，完整的 SPI 提供者和使用者的实现过程演示完毕。这个示例非常简单，但却是 Dubbo 中实现微内核架构的基础。接下来，就让我们把话题转换到 Dubbo，看看 Dubbo 中应用 SPI 机制的具体方法。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-21&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;微内核模式在-dubbo-中的应用&quot;&gt;&lt;a href=&quot;#%E5%BE%AE%E5%86%85%E6%A0%B8%E6%A8%A1%E5%BC%8F%E5%9C%A8-dubbo-%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;微内核模式在 Dubbo 中的应用&lt;/h3&gt;
&lt;p&gt;Dubbo 中应用微内核模式的方法也是基于 SPI，但 Dubbo 对 JDK 中的 SPI 机制做了优化，并添加了一些扩展功能。&lt;/p&gt;
&lt;p&gt;首先，Dubbo 提供了一个 @SPI 扩展点注解，该注解是理解 Dubbo SPI 机制的基础。在 Dubbo 中，只有添加了 @SPI 注解的接口类才会去查找扩展点实现。&lt;/p&gt;
&lt;p&gt;我们随处可以看到 @SPI 注解的应用场景。例如，以常见的 Protocol 接口为例，在该接口上使用的就是 &lt;code class=&quot;language-text&quot;&gt;@SPI(&amp;quot;dubbo&amp;quot;)&lt;/code&gt; 注解，代表该接口是一个扩展点，同时该扩展点的默认实现是 DubboProtocol，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27983643429860440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;dubbo&amp;quot;)
public interface Protocol`, `27983643429860440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dubbo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们已经知道 JDK 只会把 SPI 配置存放在 META-INF/services/ 这个目录下，而 Dubbo 则提供了三个类似这样的目录，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-58-18-61dd067885af51ab8b92776514a54b4f-d60ed.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 613px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 35.07340946166394%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABa0lEQVQoz42R227TQBRF/f+/kZZclKSFPjSCIgJSsB3iW3y3x9dEpDxUQuUNOlk9SdU3kBhp64xmNGfW3sfQWqOPf9eTfuK0miahaxei99jmG8LtFZ47xd1McERReM1+94EguMHgP9auVyi1JPBv2QZ3lMUX4uijNL6TxguybMnDg09T2xiO8nB2Pt4uwOm8F7UuTu+xVhvuf9xzOHzHdSySxKdtciHOaducvi+FTNF3JUURyVmFMXBHjKs5w2LKSM0YxEOG5ZT5/oZJfcU2i7CtT/x6/ExVvmWzHhAGY/ngQuxenKvnXPL4c0mWimUrt/laWaxKU6rNSpnnvSn4q8Kk2/dC0ZOmrsijrhKqKpYIIqEKxb6oPNGlQqwkQw36t+b458jxtYpezvQ5w8OhE5JbIVqIvW+SlSV1fd63jS3DWotsicLBkOf/HMbrXV2HqHJOEs1QxTVpPBPbI3z3NPEx0XZCns1I4nc8A1yF9EE34qg5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 58 18&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-58-18-61dd067885af51ab8b92776514a54b4f-d60ed.png&quot; data-srcset=&quot;/static/2024-12-31-10-58-18-61dd067885af51ab8b92776514a54b4f-a256c.png 200w,
/static/2024-12-31-10-58-18-61dd067885af51ab8b92776514a54b4f-56b73.png 400w,
/static/2024-12-31-10-58-18-61dd067885af51ab8b92776514a54b4f-d60ed.png 613w&quot; data-sizes=&quot;(max-width: 613px) 100vw, 613px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;作为示例，我们继续围绕 Dubbo 中的 Protocol 接口展开讨论。针对 Protocol 接口，Dubbo 提供了包括 GrpcProtocol、DubboProtocol 在内的多个实现类，并通过 SPI 机制完成对具体某种实现方案的加载过程。让我们分别来到提供这些实现类的代码工程 dubbo-rpc-grpc 和 dubbo-rpc-dubbo，会发现在 META-INF/dubbo/internal/ 目录下都包含了一个 com.apache.dubbo.rpc.Protocol 配置文件。&lt;/p&gt;
&lt;p&gt;其中，dubbo-rpc-grpc 工程的代码结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-58-59-122efb8516874604704ae7f7b6c79530-97065.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 786px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 38.167938931297705%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAAy0lEQVQY05WO2wrCMAyG+/5vpYKHOydWVpu108mma3d0dkxrZ1AQQXaxn+SDhD8HEsogTmKlVZwkzrl+jIi5ZpfzyZgWi9HDmFV2EgJM244eRntn6ugYcACt1dNa96d+YCl5f9ubWgvhgxTW2n+TGxD5Om7FBTbrA/Mbpa5p2qjUaP1hf38MXv585bou9jw6m26nEyQGXy3Ycg7LhY6isirzPCvKAolCVnVFfjepPN8DZ8AhwITwGMpQCikY33PsM58jmE/plu4oAH8B6EDKFAXG7MMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 58 59&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-58-59-122efb8516874604704ae7f7b6c79530-97065.png&quot; data-srcset=&quot;/static/2024-12-31-10-58-59-122efb8516874604704ae7f7b6c79530-43f99.png 200w,
/static/2024-12-31-10-58-59-122efb8516874604704ae7f7b6c79530-da390.png 400w,
/static/2024-12-31-10-58-59-122efb8516874604704ae7f7b6c79530-97065.png 786w&quot; data-sizes=&quot;(max-width: 786px) 100vw, 786px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;类似的，dubbo-rpc-dubbo 工程的代码结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-10-59-29-d69cf4df43bef0e654ccec54ca75d19c-2b2cd.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 513px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.165692007797276%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABZ0lEQVQoz51TaW+CQBDl//+nNqlt9GO9OHZBEDXhUECQ+3odoLVa04R2srNvsuy+2Tc7CGmc4LA1wLgCx3HIbXCVw/N8dNa2be8UYYwJ3ZSnEY72DsE5RF3XV6J7QowlbNHQ/jILYW412K57/XhLOprwa3NdV4hOe6iaCvfoIi/yf5EOhOR13SC9+DB1hsVqCd3QEcfxnewxLtxmb0h7Hvs4mCoUxpCmKf5qwk9JddMQqQfD2EDTdURRhLIskWVZj2VVoSIvigJ5nvfrSZL0aro14bFOQ+wHATZUT1FagzEZKyoDVxnFDLIsgfMBDWq5rtUsy+prL9y+aDsE3cApOGNDt1TooKZ/4+5wAKNEKsWSIsOyLepZDw51x/WGD21Cso+UUZVESMs54Rri/B1svYLBeY8+/QRJGPZSw/CMkEpTlL8QNpcL3PkC/G0C8fkJ/HVAZfICfTaDNp3CnE2R7/ZfBz8ftcEH8BFOwZ1LiLoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 10 59 29&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-10-59-29-d69cf4df43bef0e654ccec54ca75d19c-2b2cd.png&quot; data-srcset=&quot;/static/2024-12-31-10-59-29-d69cf4df43bef0e654ccec54ca75d19c-2a6c9.png 200w,
/static/2024-12-31-10-59-29-d69cf4df43bef0e654ccec54ca75d19c-aed38.png 400w,
/static/2024-12-31-10-59-29-d69cf4df43bef0e654ccec54ca75d19c-2b2cd.png 513w&quot; data-sizes=&quot;(max-width: 513px) 100vw, 513px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们分别打开这两个工程的 com.apache.dubbo.rpc.Protocol 配置文件，可以发现它们分别指向了 org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol 和 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82262879562593320000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// dubbo-rpc-grpc 工程
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol

// dubbo-rpc-dubbo 工程
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol`, `82262879562593320000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// dubbo-rpc-grpc 工程
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol

// dubbo-rpc-dubbo 工程
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当 Dubbo 在引用具体某一个代码工程时，就可以通过该工程中的配置项找到 Dubbo 接口对应的扩展点实现。&lt;/p&gt;
&lt;p&gt;接下来，就让我们深入探讨 Dubbo 中扩展点的加载机制，为了加载扩展点，Dubbo 专门提供了一个 ExtensionLoader 类，如果我们想要获取 DubboProtocol 的实现类，可以使用如下代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62349442377192970000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`DubboProtocol dubboProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);`, `62349442377192970000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;DubboProtocol&lt;/span&gt; dubboProtocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DubboProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们来看一下 getExtension 方法的细节，该方法的具体实现过程如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;81573414534357780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public T getExtension(String name) {
   if (name == null || name.length() == 0) {
      throw new IllegalArgumentException(&amp;quot;Extension name == null&amp;quot;);
   }

   if (&amp;quot;true&amp;quot;.equals(name)) {
      return getDefaultExtension();
   }

   Holder&lt;Object&gt; holder = cachedInstances.get(name);
   if (holder == null) {
      cachedInstances.putIfAbsent(name, new Holder&lt;Object&gt;());
      holder = cachedInstances.get(name);
   }

   Object instance = holder.get();
   if (instance == null) {
      synchronized (holder) {
         instance = holder.get();
         if (instance == null) {
            instance = createExtension(name);
            holder.set(instance);
         }
      }
   }
   return (T) instance;
}`, `81573414534357780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Extension name == null&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDefaultExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;Holder&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; holder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cachedInstances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;holder &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      cachedInstances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putIfAbsent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Holder&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      holder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cachedInstances&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; holder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;instance &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;holder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; holder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;instance &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            holder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;instance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们看到这里用到了缓存，该方法会首先检查缓存中是否已经存在扩展点实例，如果没有则通过 createExtension 方法进行创建。我们一路跟踪 createExtension 方法，发现它又调用了 getExtensionClasses 方法，而 getExtensionClasses 方法内部又使用了 loadExtensionClasses 方法。在 loadExtensionClasses 方法中，我们终于看到了熟悉的 SPI，该方法如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;8427010170414895000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private Map&lt;String, Class&lt;?&gt;&gt; loadExtensionClasses() {
   final SPI defaultAnnotation = type.getAnnotation(SPI.class);
   if (defaultAnnotation != null) {
      // 确定缓存名称
   }

   Map&lt;String, Class&lt;?&gt;&gt; extensionClasses = new HashMap&lt;String, Class&lt;?&gt;&gt;();
   loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
   loadFile(extensionClasses, DUBBO_DIRECTORY);
   loadFile(extensionClasses, SERVICES_DIRECTORY);
   return extensionClasses;
}`, `8427010170414895000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadExtensionClasses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SPI&lt;/span&gt; defaultAnnotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SPI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;defaultAnnotation &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 确定缓存名称&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; extensionClasses &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;loadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extensionClasses&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DUBBO_INTERNAL_DIRECTORY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;loadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extensionClasses&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DUBBO_DIRECTORY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;loadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;extensionClasses&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SERVICES_DIRECTORY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; extensionClasses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在这里，我们注意到调用了三次 loadFile 方法，分别对应 META-INF/ 目录下的三个子目录。在 loadFile 方法中，可以看到 Dubbo 是直接通过 Class.forName 的反射机制加载这些 SPI 的扩展类，并进行缓存。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-中的扩展点&quot;&gt;&lt;a href=&quot;#dubbo-%E4%B8%AD%E7%9A%84%E6%89%A9%E5%B1%95%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 中的扩展点&lt;/h3&gt;
&lt;p&gt;在 Dubbo 中存在一大批扩展点，这些扩展点对应于 RPC 架构中的不同组件。事实上，很多 Dubbo 内置的功能组件也都被设计成了扩展点，从而可以被框架的开发人员按需进行替换。&lt;/p&gt;
&lt;p&gt;要想知道 Dubbo 中到底包含了的所有扩展点，我们可以整合所有工程中 META-INF/dubbo/internal/ 目录下的配置文件来进行获取。下图展示了 Dubbo 扩展点分层图：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-12-31-11-00-05-7071a069985c0c24a1365242e074e44e-a40ca.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 647px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 49.45904173106646%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACMUlEQVQoz42SW2+iUBSF+f+/YB5mMum0drQVKyigaCe2WrWt4gWnFcG71l5iOwgqXtdsThOTeRuSHQ6HnHW+tffikq00lLaKeDuB2liHa7vodrPodlQ09DiajzKGwww+Pt7wPw8XrkdxdB/AsR7EXb8E948Lw0iiXDpF6e4ntHIQbTOO5+cB9vsdNpsVtts11usVVivv8P2554GTrCTkcQrySwqVSQ321Ean8wvjkYp6lUe/l8RomKILwjBbClGLeHyQmIN6LUZ7Kkr3ETz8lvH0lAUXNxMQewqiAwnauIrZ+wyaJuK2eIJi/oRR+lUsHNNhAddX30k0DMsUqep4nz5jMhng9XWE6XQCTmxKiA0SEIYySkMN9ruNRkOGXj9H7voIteo5rXnkb06IKoFc9pgIReqtiJtckFpyQfQR3N2GYTSVT0KhI0MYyIzQt2wYKgmdEeUpHQgRWZwo+tSnDRaLOTxvyfrneQs4zgyu67D3cjkH9yN3im83R/iS/YqckYfz4aBlqMxWgSxXtBDaloRer8XEHMc+lOv6Yvah/D1O6aaQfssg8XaJ6ovOCC0rzXpUzAfImkCEMephgC6KoUDWHx+i9F9CuczT8DLMdttS8TS+AsfrAs6bUQSb/CE2pplmhL5dv4eWGaOJXqDfvaSJ8iSsMCF/EPvdllnfUGS22xUNRZcQqvA40yPQBlXMZ3OKzTUJRIiQBCtRdCj4fg53uz0d9lgGN5v1P4He7z/rLwjfv9x4fWXRAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 12 31 11 00 05&quot; title=&quot;&quot; data-src=&quot;/static/2024-12-31-11-00-05-7071a069985c0c24a1365242e074e44e-a40ca.png&quot; data-srcset=&quot;/static/2024-12-31-11-00-05-7071a069985c0c24a1365242e074e44e-3426a.png 200w,
/static/2024-12-31-11-00-05-7071a069985c0c24a1365242e074e44e-f3a4f.png 400w,
/static/2024-12-31-11-00-05-7071a069985c0c24a1365242e074e44e-a40ca.png 647w&quot; data-sizes=&quot;(max-width: 647px) 100vw, 647px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们无意对上图中所有的扩展点都做详细展开，在这里，我们选择 LoadBalance 作为我们的扩展点示例，以此介绍 Dubbo 中扩展点的具体实现方法。&lt;/p&gt;
&lt;p&gt;首先，让我们来看看 LoadBalance 接口的定义，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;757905525311053300&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
   &lt;T&gt; Invoker&lt;T&gt; select(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation) throws RpcException;
}`, `757905525311053300`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RandomLoadBalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadBalance&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invokers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果我们在 dubbo-cluster 工程中找到 META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.LoadBalance 配置文件，不难想象该文件中定义了我们已知的四个 LoadBalance 扩展实现，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2591620633718827500&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance`, `2591620633718827500`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，在 LoadBalance 接口上存在 @SPI 注解，代表它是一个扩展点。而该解中的参数 RandomLoadBalance.NAME 表示该扩展点的默认实现是随机（random）算法，关于 Dubbo 中负载均衡算法，你可以结合之前做一些回顾。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-23&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-23&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;对于微内核架构而言，我们首先需要明确它的组成作用。面试官会考查候选人对微内核架构基本概念的理解程度，该架构的作用还是提供了一个可以高度扩展的实现机制，是很多开源框架中实现系统扩展性的首选架构模式。从知识体系上讲，关于微内核架构的基本概念本讲都做了详细的介绍，回答时只要有条理地进行说明即可。&lt;/p&gt;
&lt;p&gt;回答这类问题时的一个建议是，可以把设计思想拔高到架构层次，从扩展性角度进行切入。同时，微内核架构确实应用非常广泛，像 Dubbo 框架以及主流的分库分表中间件 ShardingSphere 等开源框架都用到了这个架构模式。如果能够有所发散，展示一些自己的知识面，是一种加分策略。&lt;/p&gt;
&lt;p&gt;关于 SPI 机制也是常见的考查点。我们首先要明白 API 和 SPI 的区别，API 面向的是框架的用户，而 SPI 面向的是框架的开发人员。SPI 在本质上是提供给服务提供商与扩展框架功能的开发者使用的接口。&lt;/p&gt;
&lt;p&gt;回答这个问题时，我们可以先围绕 SPI 的定义，即服务提供接口 Service Provider Interface 来进行展开。然后，结合微内核架构的设计思想给出进一步的说明。本讲介绍了 SPI 机制的一种具体实现，即基于 JDK 的 ServiceLoader 类完成 SPI 实现的加载和管理，并给出了基于 ServiceLoader 类的具体使用案例。这也是理论联系实践的一种介绍方法，即先阐述理论知识，然后再给出案例的说明即可。当然，如果你对 JDK 的 ServiceLoader 类的实现原理有兴趣，也可以进行进一步的深入学习，我们在本讲内容中也基于 Dubbo 框架再次对微内核模式以及 ServiceLoader 类进行讨论。&lt;/p&gt;
&lt;p&gt;结合 SPI 和 Dubbo 框架，面试官会考查 Dubbo 中实现的 SPI 机制与 JDK 中默认的方式的不同点。这个问题也比较典型，在实际面试过程中经常被问到。针对这个问题，我们也需要明确，所谓的 SPI 机制只是一种设计理念，而具体的实现策略视框架的不同会有所差别。虽然 Dubbo 也采用了 SPI 机制，也是从 JAR 中加载对应的扩展类，但它的实现方式与 JDK 中基于 ServiceLoader 是不一样的。我们需要从这两方面的差异点出发来梳理这个问题的解答思路。&lt;/p&gt;
&lt;p&gt;基于本讲中给出的内容，就加载 SPI 实例的配置文件位置而言，Dubbo 支持更多的加载路径。同时，Dubbo 采用的是直接通过名称对应的 Key 值来定位具体的实现类，而 ServiceLoader 内部使用的是一个迭代器，在获取目标接口的实现类时只能通过遍历的方式把配置文件中的类全部加载并实例化，显然效率比较低下。Dubbo 中专门采用了与 JDK 不同的 SPI 实现机制，主要目的就是克服这种效率低下的情况，并提供更多的灵活性。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-21&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;本讲内容我们剖析分布式主流开源框架中所采用的一种常见架构模式，即微内核架构模式。对于微内核架构模式，我们关注它的基本概念以及 JDK 中所提供的 SPI 实现方式。而结合具体的开源框架，我们发现 Dubbo 中实现微内核架构采用了与 JDK 所提供的这套机制类似的设计思路，但并没有直接照搬而是重新提供了一套自己的实现机制。&lt;/p&gt;
&lt;p&gt;下一讲，我们继续讨论分布式系统构建过程中另一种具体代表性的架构模式，即管道-过滤器模式。可以说，管道-过滤器是所有请求-响应类框架中的标准组件。那么，为什么它会应用如此广泛？这个架构模式到底能用来解决什么问题呢？我们下一讲中再聊。&lt;/p&gt;
&lt;h1 id=&quot;流程定制：管道-过滤器架构能用来解决什么问题？&quot;&gt;&lt;a href=&quot;#%E6%B5%81%E7%A8%8B%E5%AE%9A%E5%88%B6%EF%BC%9A%E7%AE%A1%E9%81%93-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%9E%B6%E6%9E%84%E8%83%BD%E7%94%A8%E6%9D%A5%E8%A7%A3%E5%86%B3%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;流程定制：管道-过滤器架构能用来解决什么问题？&lt;/h1&gt;
&lt;p&gt;在上一讲中，我们基于微内核架构模式讨论了系统扩展性的实现方式。掌握架构模式的难度在于每个模式看上去都能够理解但就是不知道如何应用，固然在日常开发过程中多尝试应用这些模式有助于提高自身的设计能力，但通过阅读源代码来加深对架构模式的理解和掌握是一条捷径。&lt;/p&gt;
&lt;p&gt;在本讲内容中，我们将继续引出另一种可以用来实现系统扩展性的架构模式，这就是管道-过滤器模式。和微内核架构相比，管道-过滤器模式更加通用，几乎所有请求-响应式的系统中都可以用到这种架构模式。也正是因为这一点，管道-过滤器模式也经常出现在各大公司的面试环节。&lt;/p&gt;
&lt;p&gt;那么，什么是管道-过滤器架构？它究竟能用来解决什么问题？本讲内容将给出答案。&lt;/p&gt;
&lt;h2 id=&quot;问题背景-21&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF-21&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题背景&lt;/h2&gt;
&lt;p&gt;很多系统都存在固定的主流程，执行该主流程就能完成系统的核心功能。但在现实开发过程中，我们往往希望在主流程中添加一些附加流程，一方面确保主流程的执行不受影响，另一方面又可以通过这些额外流程来完成一些定制化需求。通常，这里的主流程面向业务核心功能，而附加流程则更多关注非功能性需求或扩展性需求。管道-过滤器模式就是用来应对这类场景。&lt;/p&gt;
&lt;p&gt;下图展示了这一层抽象概念：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-01-08-11-34-07-2f96774ebb6b733040d7ddf36e54b84e-0e897.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 578px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 86.50519031141869%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsSAAALEgHS3X78AAACM0lEQVQ4y4WUeY8SQRDF5/t/GDXqHybGI05Y0MixhBWG+77vmxng2b82jaxZoZKhe3qqXr2qV42XTL7WcPBFv3JvVAzeq9v5qKenrzqfZexsVruxq9uHYajHx3fq9z4pl32lQv6tBv3PSqU+yAsCX9NJWtWqr2bjQaPhT9VqmWeAPKfT6QIYRZHKpQeNR0nV6zF12t81HqdULP6QF4ZH7XahVqutttuD9vtIx+OfwFsWRSfD9GRjiWPlzHMO+/3ePDvD4mQAIx0OoS1tPp8bxjUVCgXNZjPL7nA4WJ/tdqNKpXJhjnmupHa7rWazaQGm06nW67UJ2BrmK41GI/X7fS0WC3u22WwMo519TyQSWi6XfwGPx6Opf6xcLqdWqyXeASXglkECxrBvNBo2CUQ8Suh2u/J93whT1WQyubC7Vvf6wUhcKpUUj8ctIJVw5tEPKFPSYDCw/QGQjPcAiaGHsIQtk+DxAwCHnU7HAtECRLplANJDYui/66nnMlIqPcDxevZw5BvCsLrvfMNIzrmr5gJI2bFYzIoDCCWQkTaUy2UFQWBXknKOcPSN/rN/NjbYcDi0KmMu+z2V8aOPvV7PzixEPMcuk8mYa1S3h2R0Wf8nCn7ZbNbGwZJpAdxzffC/+XYMKAdBWF9i6gDxyefzSqfT9hZRPm26MER+qLNndO4BuhtDq2CIL3vPfeTaIQCZyQbgNcC/gKjNuDHUxDohL6JQP/KTiQZf/w++xNKdk/z6mv4GsqcZWsHku5wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 01 08 11 34 07&quot; title=&quot;&quot; data-src=&quot;/static/2025-01-08-11-34-07-2f96774ebb6b733040d7ddf36e54b84e-0e897.png&quot; data-srcset=&quot;/static/2025-01-08-11-34-07-2f96774ebb6b733040d7ddf36e54b84e-d60f6.png 200w,
/static/2025-01-08-11-34-07-2f96774ebb6b733040d7ddf36e54b84e-6689e.png 400w,
/static/2025-01-08-11-34-07-2f96774ebb6b733040d7ddf36e54b84e-0e897.png 578w&quot; data-sizes=&quot;(max-width: 578px) 100vw, 578px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;可以看到，我们在请求和响应的前后添加一组定制化组件。上图看上去比较简单，但实现过程其实是很有难度的，我们需要考虑的点有很多，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何在主流程中找到合适的切入点来添加定制化组件？&lt;/li&gt;
&lt;li&gt;这些定制化组件如何不影响到主流程的执行过程？&lt;/li&gt;
&lt;li&gt;有多个定制化组件时，我们如何管理它们的执行顺序？&lt;/li&gt;
&lt;li&gt;如何确保上一个定制化组件的输出能够成为下一个定制化组件的输入？&lt;/li&gt;
&lt;li&gt;这些定制化组件之间如何确保足够的解耦？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;围绕这些问题，我们可以采用很多不同的实现策略。而管道-过滤器架构天生就为我们管理上图中的各种定制化组件提供了解决方案。围绕管道-过滤器架构，我们也可以引出一些常见的面试题，如下所示：&lt;/p&gt;
&lt;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;Dubbo 中是如何实现管道-过滤器架构的？&lt;/li&gt;
&lt;li&gt;如果想要在 Dubbo 中实现一个自定义的过滤器，你会怎么做？&lt;/li&gt;
&lt;li&gt;Mybatis 中是如何实现管道-过滤器架构的？&lt;/li&gt;
&lt;li&gt;如果想要在 Mybatis 中实现一个自定义的过滤器，你会怎么做？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对不同的框架，类似上面所示的这些面试题还可以罗列很多。这些面试题既有概念，又有实践，有些还和日常开发过程中的一些需求息息相关，需要我们对这些问题背后的考点做细化分析。&lt;/p&gt;
&lt;h2 id=&quot;问题分析-22&quot;&gt;&lt;a href=&quot;#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90-22&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;问题分析&lt;/h2&gt;
&lt;p&gt;我们先来把管道-过滤器架构与上一讲中介绍的微内核架构做一个对比。&lt;/p&gt;
&lt;p&gt;本质上，微内核架构使用了一种插件化机制来对系统中组件与组件之间的调用关系进行扩展，从而增强系统构建的灵活性。而在请求-响应式的系统中，管道-过滤器实现扩展性采用的是类似切面（Aspect）的设计理念。基于这一架构模式，我们可以在请求流程中嵌入那些非功能的处理逻辑，这些处理逻辑的添加和删除过程相互独立，而且对于请求流程而言是无感知的。这是我们回答这类问题的第一个要点，即管道-过滤器模式能够解决的问题以及适用的具体场景。&lt;/p&gt;
&lt;p&gt;第二个需要考虑的要点是，如何动态把握请求的处理流程？我们首先想到的是诸如适配器模式等常见的设计模式，但设计模式主要关注细粒度的微观设计，并不适合高层次的体系结构设计。从结构上讲，管道-过滤器是一种组合行为，主功能是以切面的方式实现。管道-过滤器具有高度抽象化的通用结构和功能特性。&lt;/p&gt;
&lt;p&gt;最后，不可否认，在分布式系统构建过程中，管道-过滤器可以说是不可缺少的一种技术组件。当我们在阅读一些开源框架的源码时，看到 Filter（过滤器）或 Interceptor（拦截器）等名词时（如 Dubbo 用的是 Filter，而 Mybatis 用的是 Interceptor），往往就是碰上了管道-过滤器模式。因此，应对这类问题的第三个要点就是掌握管道-过滤器模式在主流开源框架中的应用方式和实现原理。&lt;/p&gt;
&lt;h2 id=&quot;技术体系-24&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E4%BD%93%E7%B3%BB-24&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术体系&lt;/h2&gt;
&lt;p&gt;管道-过滤器架构模式是用于解决适配和扩展性问题的代表性架构模式，结构上主要包括过滤器和管道两种元素，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-01-08-11-35-37-df5b3c500a86017f100c6926ca91ea7a-54e16.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 599px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 36.39398998330551%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABhElEQVQoz22SYU/aUBSG+/+/bt/mp2WJyTKnGEYCg04nVrRCEVAo4OqmKC0tSDdoldJnh4tsWbKTNPek581z3nvO1Wy7Sr93xv3gG6u4ujzi3HyPebqDVftI4yJLGIaq9vQUM5ut82UKXbuitKeVD9Rru9StLFqp+Ibzsy0u6jrTaYTd0XmOT6icvGPk6jxODIbDoYK0miammSdJlkwmP7nuHxFHBkb5LePgQL5jtEbjEMcp0+83SKWrVftMt7MvTvfp2Vnpusd4PFZAV8COs75JkqwaHGK3M1y2MnLLHFZ1D21T3MR8HuH7LkHgvZwjUv4fYTjF8x7w3Hul9X0PLU3X8iAYigNHBD8g/YuIohnNZklcfBHXB7RaRQZ3vX/A83n8JxfgUiVWLYdefEUh/5qbm664TlksFjK/W/yRTqe9S9Xc5lf4lfZVWRaUqCVFUSyNjmX+/gaYvmy3zHUvL+CMQO7Uv1XJdQcy/E/cfi9IPSevoSBaQ9WSZCHQZwyjpEazit8U0QExlVTwKwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 01 08 11 35 37&quot; title=&quot;&quot; data-src=&quot;/static/2025-01-08-11-35-37-df5b3c500a86017f100c6926ca91ea7a-54e16.png&quot; data-srcset=&quot;/static/2025-01-08-11-35-37-df5b3c500a86017f100c6926ca91ea7a-e3250.png 200w,
/static/2025-01-08-11-35-37-df5b3c500a86017f100c6926ca91ea7a-a3cce.png 400w,
/static/2025-01-08-11-35-37-df5b3c500a86017f100c6926ca91ea7a-54e16.png 599w&quot; data-sizes=&quot;(max-width: 599px) 100vw, 599px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在管道-过滤器结构中，执行定制化功能的组件被称为过滤器，负责执行具体的业务逻辑。每个过滤器都会接收来自主流程的请求，并返回一个响应结果到主流程中。而另一方面，管道用来获取来自过滤器的请求和响应，并把它们传递到后续的过滤器中，相当于是一种通道。同时，在管道-过滤器模式中，一般都会存在一个过滤器链（Filter Chain） 的概念。过滤器链带有多个过滤器，并按照链中的顺序执行过滤器。&lt;/p&gt;
&lt;p&gt;管道-过滤器风格的一个典型应用是 Web 容器的过滤器机制。例如，下图所示的就是 Nginx 中对 HTTP 请求的响应过程，可以看到在生成最终的 HTTP 响应之前，可以通过添加多个 Filter 对由处理器所产生的输出内容进行处理。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-01-08-11-36-55-5eb36a43fe21d94ecf928d3413d8c6d4-5d925.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 508px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 71.06299212598425%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACZElEQVQ4y3VT2W7aQBTlx/gFPgaJL0A8894+VK3apmmLEqlVUhICCUlYk+CwGbM5LGEHs4XFBk7vjHEKgV7pasae4zP3nnts0jQNLBwOB8xmMxaLBX9eLpdYrVY8WRh749xut8Nqte5gTQah0+mExWLBvtgkNQhZATabbQdrWq2Wa/ASmqZiOp2g2+1CURQMBoNXAqMCVg0LRelhPB7h5WWMXq9L2eN4k8GsKEM0mx3U6y2USlWUyxUUCgXMZrOtKjfxo9EE1Wodlcoz4cuQZRkmSXpEsSjg/s6H25tTOkwjm33glb5teT5XkZUE5PMPSCQCSKdCRBJHIR+Hqqp6y3fRb5CLR0gnv0NM/6D1EJJ4iHIp9yqFQdhoNAh7gn7Pgwxh448H6LTO0GlfIJfN6ISpZJgO/IiEzxEI/IEgXCEWuyI9+lvasZhOp8iIEUhSEN6LI3g8LuRyEXoXovaHOqHR1nyukciTvdPdp2G93kW7bVz6D2MypsYmxian+2qx4z8Wk8mEqguTbmGk036I4jWKhRBpHsKg3zdss1oTjrlV3pJs7pvNJjKZY9Sqv2iIn3B7/R75nAu151Nape2WmT0Y6WaLm6nLMsfTk0hOEBEOexEKevhelpNcX06oqgt6UFEmL6VSGSLWyKwz/C+YQszrtXqba8j2659N11AQzkgHHxLxE2rjmEr3IZV0o1KWSbMpTW/Ek+nHjJtInJOl/ORBN1nMTd9ekmUu0Wo19QqDga+I3X9BKPCR1gMEbj4Q8WciTnHC4XDIk0kSjUYhxH6iJP9GkPA+7zvyrosucNNlBU74F8sXETRThJdcAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 01 08 11 36 55&quot; title=&quot;&quot; data-src=&quot;/static/2025-01-08-11-36-55-5eb36a43fe21d94ecf928d3413d8c6d4-5d925.png&quot; data-srcset=&quot;/static/2025-01-08-11-36-55-5eb36a43fe21d94ecf928d3413d8c6d4-7e12f.png 200w,
/static/2025-01-08-11-36-55-5eb36a43fe21d94ecf928d3413d8c6d4-6877e.png 400w,
/static/2025-01-08-11-36-55-5eb36a43fe21d94ecf928d3413d8c6d4-5d925.png 508w&quot; data-sizes=&quot;(max-width: 508px) 100vw, 508px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从上图中，我们可以看到管道-过滤器模式的特点在于把一系列的定制化需求转换成一种类似数据流的处理方式，数据通过管道流经一系列的过滤器，在每个过滤器中完成特定的业务逻辑。显然，每个过滤器能够独立完成自身的职责而不需要依赖于其他过滤器。这种特性使得系统的扩展性得到了巨大的提升，因为过滤器之间没有耦合度，所以我们可以很容易对现有的过滤器进行替换，而动态添加和删除过滤器也不会对整个处理流程产生任何影响。&lt;/p&gt;
&lt;p&gt;在 Dubbo 和 Mybaits 框架中都应用了管道-过滤器模式，而且也都提供了基于过滤器链的实现方式，让我们先来看看它在 Dubbo 中的应用。&lt;/p&gt;
&lt;h2 id=&quot;源码解析-22&quot;&gt;&lt;a href=&quot;#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-22&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;源码解析&lt;/h2&gt;
&lt;h3 id=&quot;管道-过滤器模式在-dubbo-中的应用&quot;&gt;&lt;a href=&quot;#%E7%AE%A1%E9%81%93-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%A8%A1%E5%BC%8F%E5%9C%A8-dubbo-%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;管道-过滤器模式在 Dubbo 中的应用&lt;/h3&gt;
&lt;p&gt;Dubbo 中的过滤器概念基本上符合我们对管道-过滤器模式的理解。在接下来的内容中，我们先来看一下 Dubbo 中过滤器链的构建过程，然后介绍 Dubbo 中现有过滤器的实现方法。&lt;/p&gt;
&lt;p&gt;Dubbo 的过滤器实现入口是在 ProtocolFilterWrapper 类中，在服务暴露和服务引用时都会使用到过滤器链。所谓的 Wrapper，顾名思义，是对扩展类的一种包装，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2025-01-08-11-37-36-abb6442e29209da625fa702618293ec3-8e7aa.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 362px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 89.22651933701657%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsSAAALEgHS3X78AAADYUlEQVQ4y31UaVfbVhT0f+/HfuiXnJ7T5CSkicGrbAnb8ibbWrwvsrxjNpOGEPACFAMhJjAdy0uBnvTDnKv37tXoau6858j1CxALIgKqgKAuIKAxrsB1uLBtI1JkzEvcCzzLz+vDBQliNoi4lYRjuxeD0fZiepXDcKJidK5hvMR8XTXdsFo+WE0f2p0ATkcZjC/0df7vaQ754ib8wm8IdUQ4pHYU1n4YQA94tBgb/4Lrv45i6LaDqJZd9jMe6s/yQBuDwxj0zAbktgSH2IqieRhhok80idYL7CxzK3Re5Ofv7GF6kYXUCMAh9xWE8u/R6omo85eewuoFUWv5YbYFmB0BFcuLYtUFqxtc18xzzb6ElLGBgCXAoZ/kkb2vIHqahm8vBP8gAv9hGAK7dpJkk/AdhG24dkT8rr+Fe1eCcCTDN69jvTiQod0WkR7qcGhfcqhTh/SFAaEdQJJEcWoa2w3hXeIPJA8i0I4TNjKf44iTWGoJdk2amkbZROxYQZWaapPsirCFyFka3sRrzKZFXE50XJ0bti7VghOl3EeU806YJRfO+PL1ZQ5TQhZfwbX1K+SjBMpkUZ8Shk9T2M59WA6hsRS7gdFIxWAQxc6OhJOTFGaz2nogn47iyBa2EBnESGj9l1BUN1jYXVhjbomlLRaT7S6stbLLj7o99f19argbQYX7a0KT3STGGj6k3iDX8EEzPdCJLJ/VqodwQ6t5EFLeIFNxwagvaoy6F8HMO4TYYWXVoU7CGgkV6pbhaSmz2wLXRUb1roz4ZdZezxE8jkP9XrFzc5S4V2ZnyTMVpQfzOWGSWmnXBShfU4jyyxlOOWD8CUF/j0x/G6muyMiJNvxQOkHIppdG9qH0WP85of6thDCP2eSLgtlNCVcTA3s0cY8kBxxKk6Y++RTHmJ7devsLvOFXMNixMtR+0uFNEfG9CO5uy/YgHin+3V0Ft9/KjFXc3JYwu6/hkXqNzjLQLWrJJpTRE0JtRThcdCh3JVyPdeDexMP3KmPNfl5H2sben+vKzlV+JEXC4o8a1LEBh3FaoKxdpM51qByKzGMkl5xQGl4k6x4kXiC5RIK/L5Q2keMvJ4cZ+6QYl3leDu045OMkJHrJWXbDbfnhrBOmz8bH/4GLl8EmbeXhLRMjR7Adwj+J0M5qdqeqNQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2025 01 08 11 37 36&quot; title=&quot;&quot; data-src=&quot;/static/2025-01-08-11-37-36-abb6442e29209da625fa702618293ec3-8e7aa.png&quot; data-srcset=&quot;/static/2025-01-08-11-37-36-abb6442e29209da625fa702618293ec3-acee0.png 200w,
/static/2025-01-08-11-37-36-abb6442e29209da625fa702618293ec3-8e7aa.png 362w&quot; data-sizes=&quot;(max-width: 362px) 100vw, 362px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Dubbo 中的包装类同样实现扩展点接口，具有与扩展点一样的方法。目前，纵观整个 Dubbo 框架，只存在一个 Wrapper，即 ProtocolFilterWrapper。ProtocolFilterWrapper 类实现了 Protocol 接口，并具有如下所示的 export 和 refer 方法实现。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1557659896025565000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public &lt;T&gt; Exporter&lt;T&gt; export(Invoker&lt;T&gt; invoker) throws RpcException {
   if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
      return protocol.export(invoker);
   }

   return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
 }

public &lt;T&gt; Invoker&lt;T&gt; refer(Class&lt;T&gt; type, URL url) throws RpcException {
   if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
      return protocol.refer(type, url);
   }

   return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}`, `1557659896025565000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REGISTRY_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildInvokerChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SERVICE_FILTER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROVIDER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REGISTRY_PROTOCOL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProtocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildInvokerChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REFERENCE_FILTER_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CONSUMER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到这两个方法都使用了一个名为 buildInvokerChain 的方法，从命名上看，该方法就是用来构建调用链，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;33074017091870390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private static &lt;T&gt; Invoker&lt;T&gt; buildInvokerChain(final Invoker&lt;T&gt; invoker, String key, String group) {
   Invoker&lt;T&gt; last = invoker;
   List&lt;Filter&gt; filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
   if (filters.size() &gt; 0) {
      for (int i = filters.size() - 1; i &gt;= 0; i--) {
         final Filter filter = filters.get(i);
         final Invoker&lt;T&gt; next = last;
         last = new Invoker&lt;T&gt;() {
            // 这里构造一个最简化的 Invoker 作为调用链的载体 Invoker
         };
      }
   }
   return last;
}`, `33074017091870390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildInvokerChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; group&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; last &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; filters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getActivateExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; group&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; last&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         last &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 这里构造一个最简化的 Invoker 作为调用链的载体 Invoker&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; last&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们在这里看到了用于获取扩展点的 ExtensionLoader。注意，这里对通过扩展点加载的过滤器进行了排序，从而确保过滤器链按设想的顺序进行执行。&lt;/p&gt;
&lt;p&gt;看完过滤器链，我们反过来看一下过滤器。Dubbo 中的 Filter 接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;41345527301459800000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI
public interface Filter {
   Result invoke(Invoker&lt;?&gt; invoker, Invocation invocation) throws RpcException;
}`, `41345527301459800000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到 Filter 接口能够获取传入的 Invoker，从而对其进行拦截和处理。针对 Filter 接口，Dubbo 中一共存在 21 个实现类，我们无意对所有这些过滤器组件做详细展开，而是挑选一个代表性的 Filter 进行介绍，这里我们选择 TokenFilter。&lt;/p&gt;
&lt;p&gt;TokenFilter 的作用很明确，即通过 Token 进行访问鉴权，通过比对 Invoker 中的 Token 和输入参数中的 Token 来判断请求是否合法，其代码实现如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82619968139398820000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class TokenFilter implements Filter {
   public Result invoke(Invoker&lt;?&gt; invoker, Invocation inv) throws RpcException {
      String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY);
      if (ConfigUtils.isNotEmpty(token)) {
         Class&lt;?&gt; serviceType = invoker.getInterface();
         Map&lt;String, String&gt; attachments = inv.getAttachments();
         String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY);
         // 比对 Token
         if (!token.equals(remoteToken)) {
            // ...
         }
      }
      return invoker.invoke(inv);
   }
}`, `82619968139398820000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TokenFilter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; inv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TOKEN_KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; serviceType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInterface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; attachments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttachments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; remoteToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attachments &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; attachments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TOKEN_KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 比对 Token&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remoteToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上述代码中，我们关注两点。首先我们看到可以通过 invoker.getUrl 方法获取 Invoker 中的 URL 对象，而我们知道 Dubbo 中的 URL 作为统一数据模型，以 Key-Value 对的形式包含了所有服务调用过程中的参数。同时，我们看到了 Invocation 对象，该对象可以理解为是一种 DTO（Data Transfer Object，数据传输对象），用来封装所需要传递的数据。这样，一方面我们通过 URL 对象获取本地 token 参数，另一方面，我们通过 Invocation 也获取了 remoteToken，从而可以执行对比和校验操作。这也是 Dubbo 中处理调用信息传递的非常常见的一种做法，我们可以在很多地方看到类似的代码。&lt;/p&gt;
&lt;p&gt;最后，让我们基于对 Dubbo 中过滤器机制的理解来实现一个自定义的过滤器组件。这个过滤器非常简单，就是记录一下 invoke 调用所使用的时间，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;71727368924795230000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class TimeLogFilter implements Filter {
   private static Logger log = LoggerFactory.getLogger(TimeLogFilter.class);

   @Override
   public Result invoke(Invoker&lt;?&gt; invoker, Invocation invocation) throws RpcException {
      long start = System.currentTimeMillis();
      Result result = invoker.invoke(invocation);
      long elapsed = System.currentTimeMillis() - start;
      if (invoker.getUrl() != null) {
         log.info(&amp;quot;[{}], [{}], {}, [{}], [{}], [{}]&amp;quot;, invoker.getInterface(), invocation.getMethodName(), Arrays.toString(invocation.getArguments()), result.getValue(), result.getException(), elapsed);
      }
      return result;
   }
}`, `71727368924795230000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeLogFilter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Logger&lt;/span&gt; log &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimeLogFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; elapsed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[{}], [{}], {}, [{}], [{}], [{}]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInterface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;invocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getArguments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; elapsed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，在 Dubbo 中，我们通过实现 Filter 接口并传入 Invoker 和 Invocation 对象就可以完成一个自定义过滤器的开发工作。&lt;/p&gt;
&lt;h3 id=&quot;管道-过滤器模式在-mybatis-中的应用&quot;&gt;&lt;a href=&quot;#%E7%AE%A1%E9%81%93-%E8%BF%87%E6%BB%A4%E5%99%A8%E6%A8%A1%E5%BC%8F%E5%9C%A8-mybatis-%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;管道-过滤器模式在 Mybatis 中的应用&lt;/h3&gt;
&lt;p&gt;在 Mybatis 中，管道-过滤器模式通过拦截器的概念进行体现。而对外进行暴露时，则用到了 Plugin 配置项。想要在 Mybatis 中使用管道-过滤器模式，那就需要在配置文件中添加类似如下所示的配置项，可以看到在 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/code&gt; 配置段中可以添加一个自定义的 interceptor 配置项。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43575117567840220000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;configuration&gt;
   &lt;plugins&gt;
      &lt;plugin interceptor=&amp;quot;com.tianyalan.mybatis.interceptor.MyInterceptor&amp;quot;&gt;
         &lt;property name=&amp;quot;prop1&amp;quot; value=&amp;quot;prop1&amp;quot;/&gt;
         &lt;property name=&amp;quot;prop2&amp;quot; value=&amp;quot;prop2&amp;quot;/&gt;
      &lt;/plugin&gt;
   &lt;/plugins&gt;
&lt;/configuration&gt;`, `43575117567840220000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;plugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;plugin&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interceptor&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.tianyalan.mybatis.interceptor.MyInterceptor&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prop1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prop1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prop2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prop2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;plugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;configuration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后，在解析 XML 配置文件的 XMLConfigBuilder 类中，我们找到了解析 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/code&gt; 配置段的代码，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82433901702642960000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private void pluginElement(XNode parent) throws Exception {
   if (parent != null) {
      for (XNode child : parent.getChildren()) {
         String interceptor = child.getStringAttribute(&amp;quot;interceptor&amp;quot;);
         Properties properties = child.getChildrenAsProperties();
         Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
         interceptorInstance.setProperties(properties);
         configuration.addInterceptor(interceptorInstance);
      }
   }
}`, `82433901702642960000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pluginElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;XNode&lt;/span&gt; parent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;XNode&lt;/span&gt; child &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChildren&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; interceptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; child&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStringAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;interceptor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Properties&lt;/span&gt; properties &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; child&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChildrenAsProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt; interceptorInstance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         interceptorInstance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         configuration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addInterceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptorInstance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在上述代码中，我们看到了 Mybatis 中代表过滤器的 Interceptor 接口。我们根据配置的 interceptor 属性实例化 Interceptor 对象，然后通过 configuration.addInterceptor 方法添加新的 Interceptor 实例。我们跟踪代码，发现 Configuration 中定义了一个如下所示的 InterceptorChain 对象，该方法就是将 Interceptor 实例添加到了 InterceptorChain 中。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;97067769524629450000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`protected final InterceptorChain interceptorChain = new InterceptorChain();`, `97067769524629450000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterceptorChain&lt;/span&gt; interceptorChain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterceptorChain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样，管道-过滤器模式中代表过滤器（Interceptor）和过滤器链（InterceptorChain）的相关对象都出现了，让我们首先从这些对象的定义和实现入手探究 Mybatis 的内部原理。&lt;/p&gt;
&lt;p&gt;在 Mybatis 中，Interceptor 和 InterceptorChain 都位于 org.apache.ibatis.plugin 包中，其中 Interceptor 是个接口，InterceptorChain 是个实体类，它们的代码看上去都不多。让我们先来看一下 InterceptorChain 类，如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34825488499696312000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class InterceptorChain {
   private final List&lt;Interceptor&gt; interceptors = new ArrayList&lt;&gt;();

   public Object pluginAll(Object target) {
      for (Interceptor interceptor : interceptors) {
         target = interceptor.plugin(target);
      }
      return target;
   }

   public void addInterceptor(Interceptor interceptor) {
      interceptors.add(interceptor);
   }

   public List&lt;Interceptor&gt; getInterceptors() {
      return Collections.unmodifiableList(interceptors);
   }
}`, `34825488499696312000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterceptorChain&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; interceptors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pluginAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt; interceptor &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; interceptors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; interceptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addInterceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt; interceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      interceptors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInterceptors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unmodifiableList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，这个 InterceptorChain 同样提供了 addInterceptor 方法用于将拦截器添加到链中。不同之处在于，它在这里持有一个 interceptors 数组用于把新加入的 Interceptor 添加到这个数组中。通过这种方式，在 pluginAll 方法中就可以直接遍历 interceptors 数组并基于每个 interceptor 执行拦截逻辑。&lt;/p&gt;
&lt;p&gt;在 InterceptorChain 中，我们尚不明确的就是 interceptor.plugin(target) 方法的逻辑，让我们把思路跳转到 Interceptor 接口，该接口定义如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22399387841218530000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface Interceptor {
   Object intercept(Invocation invocation) throws Throwable;

   default Object plugin(Object target) {
      return Plugin.wrap(target, this);
   }

   default void setProperties(Properties properties) {
      // NOP
   }
}`, `22399387841218530000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt; invocation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Properties&lt;/span&gt; properties&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// NOP&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，Interceptor 接口中的 plugin 方法实际上存在一个默认实现，这里它通过 Plugin.wrap 方法完成对目标对象的拦截。Plugin.wrap 是一个静态方法，位于 Plugin 类中，Plugin 类的核心代码如下所示：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29370445321121673000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class Plugin implements InvocationHandler {
   // 省略变量定义和构造函数
   public static Object wrap(Object target, Interceptor interceptor) {
      Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; signatureMap = getSignatureMap(interceptor);
      Class&lt;?&gt; type = target.getClass();
      Class&lt;?&gt;[] interfaces = getAllInterfaces(type, signatureMap);
      if (interfaces.length &gt; 0) {
         return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
      }
      return target;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
         Set&lt;Method&gt; methods = signatureMap.get(method.getDeclaringClass());
         if (methods != null &amp;&amp; methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
         }
         return method.invoke(target, args);
      } catch (Exception e) {
         throw ExceptionUtil.unwrapThrowable(e);
      }
   }

   // 省略 getSignatureMap 方法和 getAllInterfaces 辅助方法
}`, `29370445321121673000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InvocationHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 省略变量定义和构造函数&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Interceptor&lt;/span&gt; interceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; signatureMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getSignatureMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interceptor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; interfaces &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAllInterfaces&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signatureMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interfaces&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newProxyInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; interfaces&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; interceptor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signatureMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt; proxy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; methods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signatureMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclaringClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;methods &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; interceptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;intercept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invocation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; method&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExceptionUtil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrapThrowable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 省略 getSignatureMap 方法和 getAllInterfaces 辅助方法&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;显然，我们看到了熟悉的 InvocationHandler 接口和 Proxy.newProxyInstance 方法，从而明白原来用到了 JDK 的动态代理机制。&lt;/p&gt;
&lt;p&gt;这里我们分别通过 getSignatureMap 和 getAllInterfaces 方法获取接口和方法定义的元数据，并通过动态代理机制产生代理。另一方面，我们实现了 InvocationHandler 接口的 invoke 方法，并判断是否需要对目标方法进行拦截。如果是，则调用 Interceptor.intercept 方法，在该方法中我们就可以加入任何想要加入的业务逻辑；如果不是，则调用 method.invoke 方法执行原来逻辑。&lt;/p&gt;
&lt;p&gt;这里有一点要注意，在 Mybatis 中，拦截器只能拦截 ParameterHandler、StatementHandler、ResultSetHandler 和 Executor 这四种类型的接口，这点在 Configuration 类中是通过代码预先定义好的。如果我们想要自定义拦截器，也只能围绕上述四种接口添加逻辑。&lt;/p&gt;
&lt;h2 id=&quot;解题要点-24&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E9%A2%98%E8%A6%81%E7%82%B9-24&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解题要点&lt;/h2&gt;
&lt;p&gt;在回答与管道-过滤器模式相关的面试题是，常见会碰到一些概念类问题，例如“管道-过滤器模式的应用场景有哪些？列举你知道的用到管道过滤器模式的开源框架？”这种，这些问题看上去有点复杂，实际上就是考查你对管道过滤器模式相关基本概念的理解，从考点上讲是比较明确的。我们明确，管道过滤器模式的主要应用场景在于请求-响应型的处理过程，尤其适用于处理 Web 请求。只要具备这一思路，这道题的答案也是自然而然就能得出的。在日常能够接触到的开源框架中，处理 Web 请求的框架基本上都会或多或少采用了管道过滤器模式，包括但不限于我们将要介绍的 Dubbo，以及诸如 Netty、Spring MVC、Tomcat 等常见框架。&lt;/p&gt;
&lt;p&gt;另一方面，针对具体的开源框架，面试官的提问方式可以围绕 Mybatis 等框架中的某一个功能特性展开讨论，例如“Mybatis 中的拦截器如何对执行过程进行拦截？”。针对这类问题，我们首先需要明确背后的考点实际上就是代理机制。在主流的开源框架中，涉及到过滤、拦截等常见下的实现原理往往都有代理模式相关。只要掌握动态代理机制，不同的问法都是类似的解答思路。事实上，Mybatis 中实现拦截的基本原理还是基于动态代理机制，通过获取对应方法的签名信息以及输入的接口和参数来生成代理。Mybatis 中的代理机制实现方式就是采用的 JDK 动态代理，你可以结合第 21 讲内容做一些回顾。掌握了该类的实现过程，都可以结合自己的理解对代理机制进行展开。&lt;/p&gt;
&lt;h2 id=&quot;小结与预告-22&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E7%BB%93%E4%B8%8E%E9%A2%84%E5%91%8A-22&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小结与预告&lt;/h2&gt;
&lt;p&gt;在本讲中，对于管道-过滤器架构模式，我们详细给出了该模式的基本概念和组成结构。现实中，管道-过滤器在实现上也有很多变种，我们分别基于 Dubbo 和 Mybatis 这两款主流开源框架来分析这一架构模式的具体实现方式和技巧。&lt;/p&gt;
&lt;p&gt;管道-过滤器是我们通用技术组件部分的最后一个技术组件。介绍完通用技术组件之后，我们将进入到一个扩展模块，专门讨论剖析开源框架代码结构的系统方法。在下一讲中，我们将讨论“如何基于组件设计原则剖析开源框架代码结构？”这一话题。&lt;/p&gt;
&lt;p&gt;// TODO &lt;a href=&quot;https://juejin.cn/book/7106442254533066787/section/7107604658914328588&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://juejin.cn/book/7106442254533066787/section/7107604658914328588&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Python 语法解析器实现复杂搜索]]></title><description><![CDATA[背景 针对 postgres 某个表里面的 labels 字段（labels 字段是一维数组类型）做复杂查询，要求支持常见的且，或，非等等功能 原来的业务逻辑已实现了且，或，非功能，但不支持括号来提高运算符的优先级 方案 显而易见的方案就是实现一个 python…]]></description><link>https://blog.towavephone.com/python-search-by-syntax/</link><guid isPermaLink="false">https://blog.towavephone.com/python-search-by-syntax/</guid><pubDate>Wed, 15 May 2024 18:09:57 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;针对 postgres 某个表里面的 labels 字段（labels 字段是一维数组类型）做复杂查询，要求支持常见的且，或，非等等功能&lt;/li&gt;
&lt;li&gt;原来的业务逻辑已实现了且，或，非功能，但不支持括号来提高运算符的优先级&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;方案&quot;&gt;&lt;a href=&quot;#%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;方案&lt;/h1&gt;
&lt;p&gt;显而易见的方案就是实现一个 python 版本的语法解析器来支持各种语法，有以下方案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dabeaz/ply&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;ply&lt;/a&gt; 语法简单易懂，上手有一定难度&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pyparsing/pyparsing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;pyparsing&lt;/a&gt; 语义结构化比较好，比 ply 上手难度要高&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综上，采用方案 1&lt;/p&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;p&gt;由于后端使用的 django 框架，根据数据库的不同以及 orm 的不同，操作数据库一般有 3 种形式&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原生 postgres sql 查询&lt;/li&gt;
&lt;li&gt;django Q 对象查询&lt;/li&gt;
&lt;li&gt;原生 mongodb sql 查询&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;结合业务，只需要实现 1, 2，主要功能如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;() - , | % 分别代表左括号、右括号、非、且、或、模糊搜索，优先级 () &gt; % &gt; - &gt; , &gt; |&lt;/li&gt;
&lt;li&gt;如果要搜索的 label 带有上述字符，可以用 \ 转义，不是开头的 -、% 可以不用转义&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;原生-postgres-sql-查询&quot;&gt;&lt;a href=&quot;#%E5%8E%9F%E7%94%9F-postgres-sql-%E6%9F%A5%E8%AF%A2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;原生 postgres sql 查询&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;utils/pg_sql_utils.py&lt;/code&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;24691883740555952000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import re

from pydash import py_


def replace_dashes(match):
    result = match.group()
    first_char = &apos;&apos;

    if result.startswith(&apos;-&apos;):
        first_char = &apos;-&apos;
        result = result[1:]

    return first_char + re.sub(r&apos;(?&lt;!\\)-&apos;, r&apos;\-&apos;, result)


def parse_sql(common_expression, like_expression, sql):
    # () - , | 分别代表左右括号，非，且，或，优先级 () &gt; - &gt; , &gt; |，如果要搜索的 label 带有上述 5 种字符，可以用 \ 转义，非开头的 - 不需要转义
    sql = sql.strip()
    if not sql:
        return &apos;&apos;

    # 预处理，将筛选出 - 开头的匹配项，然后对匹配项里面除了开头的 - 以外的 - 替换为 \-，最后将替换的结果拼接到原字符串
    # 就是将下面的 [^()|,-])+ 改为 [^()|,])+，在这里处理替换 - 开头的情况，即将剩余的 - 替换为 \-
    pattern = r&apos;((?:\\[()|,-])|[^()|,])+&apos;
    sql = re.sub(pattern, replace_dashes, sql)

    print(&apos;sql:&apos;, sql)

    import ply.lex as lex
    import ply.yacc as yacc

    # 定义词法分析器的词法规则
    tokens = (  # noqa: F841
        &apos;LPAREN&apos;,
        &apos;RPAREN&apos;,
        &apos;OR&apos;,
        &apos;AND&apos;,
        &apos;NOT&apos;,
        &apos;TERM&apos;,
    )

    t_LPAREN = r&apos;\(&apos;  # noqa: F841
    t_RPAREN = r&apos;\)&apos;  # noqa: F841
    t_OR = r&apos;\|&apos;  # noqa: F841
    t_AND = r&apos;,&apos;  # noqa: F841
    t_NOT = r&apos;-&apos;  # noqa: F841

    def t_TERM(t):
        # 匹配以 ()|,- 的分割的连续字符，但是要忽略转义字符 \，如 \- 表示匹配 -
        r&apos;((?:\\[()|,-])|[^()|,-])+&apos;
        # 去掉转义字符 \
        if t.value.startswith(&apos;\%&apos;):
            t.value = &apos;\%&apos; + re.sub(r&apos;\\([()|,\-%])&apos;, r&apos;\1&apos;, t.value[2:])
        else:
            t.value = re.sub(r&apos;\\([()|,\-%])&apos;, r&apos;\1&apos;, t.value)
        return t

    # 忽略空格和制表符
    t_ignore = &apos; \t&apos;  # noqa: F841

    # 错误处理
    def t_error(t):
        raise TypeError(&amp;quot;Unknown text &apos;%s&apos;&amp;quot; % (py_.get(t, &apos;value&apos;),))

    # 构建词法分析器
    lexer = lex.lex()

    # 确定运算符的优先级
    precedence = (  # noqa: F841
      (&apos;left&apos;, &apos;OR&apos;),
      (&apos;left&apos;, &apos;AND&apos;),
      (&apos;right&apos;, &apos;NOT&apos;)
    )

    # 定义语法分析器的语法规则
    def p_expression_group(p):
        &apos;&apos;&apos;expression : LPAREN expression RPAREN&apos;&apos;&apos;
        p[0] = f&apos;({p[2]})&apos;

    def p_expression_or(p):
        &apos;&apos;&apos;expression : expression OR expression&apos;&apos;&apos;
        p[0] = f&apos;{p[1]} OR {p[3]}&apos;

    def p_expression_and(p):
        &apos;&apos;&apos;expression : expression AND expression&apos;&apos;&apos;
        p[0] = f&apos;{p[1]} AND {(p[3])}&apos;

    def p_expression_not(p):
        &apos;&apos;&apos;expression : NOT expression&apos;&apos;&apos;
        p[0] = f&apos;NOT {p[2]}&apos;

    def p_expression_term(p):
        &apos;&apos;&apos;expression : TERM&apos;&apos;&apos;
        if p[1].startswith(&apos;%&apos;):
            p[0] = like_expression.format(
                value=p[1][1:])
        else:
            if p[1].startswith(&apos;\%&apos;):
                p[1] = p[1][1:]
            p[0] = common_expression.format(
                value=p[1])

    def p_error(p):
        raise SyntaxError(
            f&amp;quot;Syntax error in input! Text is {sql}, Token is {py_.get(p, &apos;value&apos;)}&amp;quot;)

    # 构建语法分析器
    parser = yacc.yacc()
    result = parser.parse(sql, lexer=lexer)
    return result


if __name__ == &apos;__main__&apos;:
    common_expression = &amp;quot;labels::TEXT[] @&gt; ARRAY [&apos;{value}&apos;]&amp;quot;
    like_expression = &amp;quot;labels::TEXT LIKE &apos;%{value}%&apos;&amp;quot;
    print(
        parse_sql(common_expression, like_expression, &apos;a|-直行路口,-free-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434&apos;))
    print(
        parse_sql(common_expression, like_expression, &apos;-ego\-turn\-right,ego-turn\-right,ego-turn-right,v4.0.4-f30-ota31-pro,v4.0.4\-f30\-ota31\-pro&apos;))`, `24691883740555952000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; re

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pydash &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; py_


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replace_dashes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    first_char &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        first_char &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-&apos;&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; first_char &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;(?&amp;lt;!\\)-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse_sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# () - , | 分别代表左右括号，非，且，或，优先级 () &gt; - &gt; , &gt; |，如果要搜索的 label 带有上述 5 种字符，可以用 \ 转义，非开头的 - 不需要转义&lt;/span&gt;
    sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 预处理，将筛选出 - 开头的匹配项，然后对匹配项里面除了开头的 - 以外的 - 替换为 \-，最后将替换的结果拼接到原字符串&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 就是将下面的 [^()|,-])+ 改为 [^()|,])+，在这里处理替换 - 开头的情况，即将剩余的 - 替换为 \-&lt;/span&gt;
    pattern &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;((?:\\[()|,-])|[^()|,])+&apos;&lt;/span&gt;
    sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; replace_dashes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sql:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lex &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; lex
    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;yacc &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; yacc

    &lt;span class=&quot;token comment&quot;&gt;# 定义词法分析器的词法规则&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;LPAREN&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;RPAREN&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;OR&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;NOT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;TERM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    t_LPAREN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\(&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_RPAREN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\)&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_OR &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\|&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_AND &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;,&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_NOT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;-&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;t_TERM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# 匹配以 ()|,- 的分割的连续字符，但是要忽略转义字符 \，如 \- 表示匹配 -&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;r&apos;((?:\\[()|,-])|[^()|,-])+&apos;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# 去掉转义字符 \&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;\\([()|,\-%])&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;\\([()|,\-%])&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; t

    &lt;span class=&quot;token comment&quot;&gt;# 忽略空格和制表符&lt;/span&gt;
    t_ignore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; \t&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 错误处理&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;t_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; TypeError&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Unknown text &apos;%s&apos;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;py_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 构建词法分析器&lt;/span&gt;
    lexer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 确定运算符的优先级&lt;/span&gt;
    precedence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;left&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OR&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;left&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NOT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 定义语法分析器的语法规则&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : LPAREN expression RPAREN&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&apos;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_or&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : expression OR expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; OR &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : expression AND expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; AND &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_not&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : NOT expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;NOT &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_term&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : TERM&apos;&apos;&apos;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; common_expression&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; SyntaxError&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Syntax error in input! Text is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, Token is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;py_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 构建语法分析器&lt;/span&gt;
    parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; yacc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;yacc&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lexer&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;lexer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    common_expression &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;labels::TEXT[] @&gt; ARRAY [&apos;{value}&apos;]&quot;&lt;/span&gt;
    like_expression &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;labels::TEXT LIKE &apos;%{value}%&apos;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        parse_sql&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;a|-直行路口,-free-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        parse_sql&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-ego\-turn\-right,ego-turn\-right,ego-turn-right,v4.0.4-f30-ota31-pro,v4.0.4\-f30\-ota31\-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84553984020941280000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`sql: a|-直行路口,-free\-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434

labels::TEXT[] @&gt; ARRAY [&apos;a&apos;] OR NOT labels::TEXT[] @&gt; ARRAY [&apos;直行路口&apos;] AND NOT labels::TEXT[] @&gt; ARRAY [&apos;free-&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;,()|space专项&apos;] AND (labels::TEXT[] @&gt; ARRAY [&apos;b&apos;] OR labels::TEXT[] @&gt; ARRAY [&apos;c&apos;]) AND (labels::TEXT[] @&gt; ARRAY [&apos;d&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;e&apos;]) AND labels::TEXT LIKE &apos;%3434%&apos; AND labels::TEXT[] @&gt; ARRAY [&apos;%3434&apos;]


sql: -ego\-turn\-right,ego\-turn\-right,ego\-turn\-right,v4.0.4\-f30\-ota31\-pro,v4.0.4\-f30\-ota31\-pro

NOT labels::TEXT[] @&gt; ARRAY [&apos;ego-turn-right&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;ego-turn-right&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;ego-turn-right&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;v4.0.4-f30-ota31-pro&apos;] AND labels::TEXT[] @&gt; ARRAY [&apos;v4.0.4-f30-ota31-pro&apos;]`, `84553984020941280000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;sql: a&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;-直行路口,-free&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-,&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;,&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;space专项,&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;d,e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,%3434,&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;%3434

labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; OR NOT labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;直行路口&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND NOT labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;free-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;,()|space专项&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; OR labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; AND &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; AND labels::TEXT LIKE &lt;span class=&quot;token string&quot;&gt;&apos;%3434%&apos;&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;%3434&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;


sql: -ego&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-turn&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-right,ego&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-turn&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-right,ego&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-turn&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-right,v4.0.4&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-f30&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-ota31&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-pro,v4.0.4&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-f30&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-ota31&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;-pro

NOT labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;v4.0.4-f30-ota31-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; AND labels::TEXT&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ARRAY &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;v4.0.4-f30-ota31-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;django-q-对象查询&quot;&gt;&lt;a href=&quot;#django-q-%E5%AF%B9%E8%B1%A1%E6%9F%A5%E8%AF%A2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;django Q 对象查询&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;utils/q_sql_utils.py&lt;/code&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;90589828094390160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import re

from pydash import py_
from django.db.models import Q


def replace_dashes(match):
    result = match.group()
    first_char = &apos;&apos;

    if result.startswith(&apos;-&apos;):
        first_char = &apos;-&apos;
        result = result[1:]

    return first_char + re.sub(r&apos;(?&lt;!\\)-&apos;, r&apos;\-&apos;, result)


def parse_q(common_expression, like_expression, sql):
    # () - , | 分别代表左右括号，非，且，或，优先级 () &gt; - &gt; , &gt; |，如果要搜索的 label 带有上述 5 种字符，可以用 \ 转义，非开头的 - 不需要转义
    sql = sql.strip()
    if not sql:
        return &apos;&apos;

    # 预处理，将筛选出 - 开头的匹配项，然后对匹配项里面除了开头的 - 以外的 - 替换为 \-，最后将替换的结果拼接到原字符串
    # 就是将下面的 [^()|,-])+ 改为 [^()|,])+，在这里处理替换 - 开头的情况，即将剩余的 - 替换为 \-
    pattern = r&apos;((?:\\[()|,-])|[^()|,])+&apos;
    sql = re.sub(pattern, replace_dashes, sql)

    print(&apos;sql:&apos;, sql)

    import ply.lex as lex
    import ply.yacc as yacc

    # 定义词法分析器的词法规则
    tokens = (  # noqa: F841
        &apos;LPAREN&apos;,
        &apos;RPAREN&apos;,
        &apos;OR&apos;,
        &apos;AND&apos;,
        &apos;NOT&apos;,
        &apos;TERM&apos;,
    )

    t_LPAREN = r&apos;\(&apos;  # noqa: F841
    t_RPAREN = r&apos;\)&apos;  # noqa: F841
    t_OR = r&apos;\|&apos;  # noqa: F841
    t_AND = r&apos;,&apos;  # noqa: F841
    t_NOT = r&apos;-&apos;  # noqa: F841

    def t_TERM(t):
        # 匹配以 ()|,- 的分割的连续字符，但是要忽略转义字符 \，如 \- 表示匹配 -
        r&apos;((?:\\[()|,-])|[^()|,-])+&apos;
        # 去掉转义字符 \
        if t.value.startswith(&apos;\%&apos;):
            t.value = &apos;\%&apos; + re.sub(r&apos;\\([()|,\-%])&apos;, r&apos;\1&apos;, t.value[2:])
        else:
            t.value = re.sub(r&apos;\\([()|,\-%])&apos;, r&apos;\1&apos;, t.value)
        return t

    # 忽略空格和制表符
    t_ignore = &apos; \t&apos;  # noqa: F841

    # 错误处理
    def t_error(t):
        raise TypeError(&amp;quot;Unknown text &apos;%s&apos;&amp;quot; % (py_.get(t, &apos;value&apos;),))

    # 构建词法分析器
    lexer = lex.lex()

    # 确定运算符的优先级
    precedence = (  # noqa: F841
      (&apos;left&apos;, &apos;OR&apos;),
      (&apos;left&apos;, &apos;AND&apos;),
      (&apos;right&apos;, &apos;NOT&apos;)
    )

    # 定义语法分析器的语法规则
    def p_expression_group(p):
        &apos;&apos;&apos;expression : LPAREN expression RPAREN&apos;&apos;&apos;
        p[0] = (p[2])

    def p_expression_or(p):
        &apos;&apos;&apos;expression : expression OR expression&apos;&apos;&apos;
        p[0] = Q(p[1]) | Q(p[3])

    def p_expression_and(p):
        &apos;&apos;&apos;expression : expression AND expression&apos;&apos;&apos;
        p[0] = Q(p[1]) &amp; Q(p[3])

    def p_expression_not(p):
        &apos;&apos;&apos;expression : NOT expression&apos;&apos;&apos;
        p[0] = ~Q(p[2])

    def p_expression_term(p):
        &apos;&apos;&apos;expression : TERM&apos;&apos;&apos;
        if p[1].startswith(&apos;%&apos;):
            p[0] = Q(**like_expression(p[1][1:]))
        else:
            if p[1].startswith(&apos;\%&apos;):
                p[1] = p[1][1:]
            p[0] = Q(**common_expression(p[1]))

    def p_error(p):
        raise SyntaxError(
            f&amp;quot;Syntax error in input! Text is {sql}, Token is {py_.get(p, &apos;value&apos;)}&amp;quot;)

    # 构建语法分析器
    parser = yacc.yacc()
    result = parser.parse(sql, lexer=lexer)
    return result


if __name__ == &apos;__main__&apos;:
    def common_expression(value): return {&apos;labels__contains&apos;: [value]}
    def like_expression(value): return {&apos;labels__regex&apos;: r&apos;%s&apos; % value}

    print(
        parse_q(common_expression, like_expression, &apos;a|-直行路口,-free-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434&apos;))
    print(
        parse_q(common_expression, like_expression, &apos;-ego\-turn\-right,ego-turn\-right,ego-turn-right,v4.0.4-f30-ota31-pro,v4.0.4\-f30\-ota31\-pro&apos;))`, `90589828094390160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; re

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pydash &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; py_
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Q


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replace_dashes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    first_char &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        first_char &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-&apos;&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; first_char &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;(?&amp;lt;!\\)-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse_q&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# () - , | 分别代表左右括号，非，且，或，优先级 () &gt; - &gt; , &gt; |，如果要搜索的 label 带有上述 5 种字符，可以用 \ 转义，非开头的 - 不需要转义&lt;/span&gt;
    sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strip&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 预处理，将筛选出 - 开头的匹配项，然后对匹配项里面除了开头的 - 以外的 - 替换为 \-，最后将替换的结果拼接到原字符串&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 就是将下面的 [^()|,-])+ 改为 [^()|,])+，在这里处理替换 - 开头的情况，即将剩余的 - 替换为 \-&lt;/span&gt;
    pattern &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;((?:\\[()|,-])|[^()|,])+&apos;&lt;/span&gt;
    sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; replace_dashes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;sql:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lex &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; lex
    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;yacc &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; yacc

    &lt;span class=&quot;token comment&quot;&gt;# 定义词法分析器的词法规则&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;LPAREN&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;RPAREN&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;OR&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;NOT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;TERM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    t_LPAREN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\(&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_RPAREN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\)&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_OR &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\|&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_AND &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;,&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
    t_NOT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;-&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;t_TERM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# 匹配以 ()|,- 的分割的连续字符，但是要忽略转义字符 \，如 \- 表示匹配 -&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;r&apos;((?:\\[()|,-])|[^()|,-])+&apos;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# 去掉转义字符 \&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;\\([()|,\-%])&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; re&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&apos;\\([()|,\-%])&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;\1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; t

    &lt;span class=&quot;token comment&quot;&gt;# 忽略空格和制表符&lt;/span&gt;
    t_ignore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; \t&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 错误处理&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;t_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; TypeError&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Unknown text &apos;%s&apos;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;py_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 构建词法分析器&lt;/span&gt;
    lexer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 确定运算符的优先级&lt;/span&gt;
    precedence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# noqa: F841&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;left&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OR&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;left&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NOT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 定义语法分析器的语法规则&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : LPAREN expression RPAREN&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_or&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : expression OR expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_and&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : expression AND expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_not&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : NOT expression&apos;&apos;&apos;&lt;/span&gt;
        p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_expression_term&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&apos;&apos;&apos;expression : TERM&apos;&apos;&apos;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;like_expression&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startswith&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\%&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;p_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; SyntaxError&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Syntax error in input! Text is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, Token is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;py_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 构建语法分析器&lt;/span&gt;
    parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; yacc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;yacc&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lexer&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;lexer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;common_expression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;like_expression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__regex&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;r&apos;%s&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        parse_q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;a|-直行路口,-free-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        parse_q&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;common_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; like_expression&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-ego\-turn\-right,ego-turn\-right,ego-turn-right,v4.0.4-f30-ota31-pro,v4.0.4\-f30\-ota31\-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;25053013304863736000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`sql: a|-直行路口,-free\-,\,\(\)\|space专项,(b|c),(d,e),%3434,\%3434

(OR: (AND: (&apos;labels__contains&apos;, [&apos;a&apos;])), (AND: (AND: (AND: (AND: (AND: (AND: (NOT (AND: (AND: (&apos;labels__contains&apos;, [&apos;直行路口&apos;])))), (NOT (AND: (AND: (&apos;labels__contains&apos;, [&apos;free-&apos;]))))), (AND: (&apos;labels__contains&apos;, [&apos;,()|space专项&apos;]))), (OR: (AND: (&apos;labels__contains&apos;, [&apos;b&apos;])), (AND: (&apos;labels__contains&apos;, [&apos;c&apos;])))), (AND: (AND: (&apos;labels__contains&apos;, [&apos;d&apos;])), (AND: (&apos;labels__contains&apos;, [&apos;e&apos;])))), (AND: (&apos;labels__regex&apos;, &apos;3434&apos;))), (AND: (&apos;labels__contains&apos;, [&apos;%3434&apos;]))))


sql: -ego\-turn\-right,ego\-turn\-right,ego\-turn\-right,v4.0.4\-f30\-ota31\-pro,v4.0.4\-f30\-ota31\-pro

(AND: (AND: (AND: (AND: (NOT (AND: (AND: (&apos;labels__contains&apos;, [&apos;ego-turn-right&apos;])))), (AND: (&apos;labels__contains&apos;, [&apos;ego-turn-right&apos;]))), (AND: (&apos;labels__contains&apos;, [&apos;ego-turn-right&apos;]))), (AND: (&apos;labels__contains&apos;, [&apos;v4.0.4-f30-ota31-pro&apos;]))), (AND: (&apos;labels__contains&apos;, [&apos;v4.0.4-f30-ota31-pro&apos;])))`, `25053013304863736000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sql&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sql&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sql line-numbers&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt;: a&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;直行路口&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;free\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;\&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;\&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;\&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;\&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;space专项&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;d&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3434&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;\&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3434&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;直行路口&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;free-&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;,()|space专项&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__regex&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;3434&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;%3434&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt;: &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ego\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;turn\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ego\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;turn\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ego\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;turn\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;v4&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.4&lt;/span&gt;\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f30\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ota31\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;pro&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;v4&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.4&lt;/span&gt;\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f30\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ota31\&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;pro

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ego-turn-right&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;v4.0.4-f30-ota31-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;labels__contains&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;v4.0.4-f30-ota31-pro&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Python深入学习]]></title><description><![CDATA[字节码与虚拟机 当每次调用函数或刚开始运行时候，建立新 frame，然后在这个 frame 的环境下一条条的运行 bytecode，每一条 bytecode 都有相应的 c 语言代码执行，在每一个 frame python 会维护一个 stack，然后 bytecode…]]></description><link>https://blog.towavephone.com/python-deep-learn/</link><guid isPermaLink="false">https://blog.towavephone.com/python-deep-learn/</guid><pubDate>Thu, 21 Mar 2024 03:08:01 GMT</pubDate><content:encoded>&lt;h1 id=&quot;字节码与虚拟机&quot;&gt;&lt;a href=&quot;#%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E8%99%9A%E6%8B%9F%E6%9C%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;字节码与虚拟机&lt;/h1&gt;
&lt;p&gt;当每次调用函数或刚开始运行时候，建立新 frame，然后在这个 frame 的环境下一条条的运行 bytecode，每一条 bytecode 都有相应的 c 语言代码执行，在每一个 frame python 会维护一个 stack，然后 bytecode 和 stack 进行交互，当然也会和 code object 保存信息进行交互，执行逻辑运算结果&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17616232560147771000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import dis


def add(a, b):
   return a + b


dis.dis(add)`, `17616232560147771000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; dis


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b


dis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dis&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;add&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1621253533470823400&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`5           0 LOAD_FAST                0 (a)
            2 LOAD_FAST                1 (b)
            4 BINARY_ADD
            6 RETURN_VALUE`, `1621253533470823400`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;           &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; LOAD_FAST                &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; LOAD_FAST                &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; BINARY_ADD
            &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; RETURN_VALUE&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70360662633240986000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1070
case TARGET(LOAD_FAST): {
   PyObject *value = GETLOCAL(oparg);
   if (value == NULL) {
         format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                              UNBOUNDLOCAL_ERROR_MSG,
                              PyTuple_GetItem(co-&gt;co_varnames, oparg));
         goto error;
   }
   Py_INCREF(value); // 引用
   PUSH(value);
   FAST_DISPATCH(); // 进入下个循环
}

// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1280
case TARGET(BINARY_ADD): {
   PyObject *right = POP();
   PyObject *left = TOP();
   PyObject *sum;
   /* NOTE(haypo): Please don&apos;t try to micro-optimize int+int on
      CPython using bytecode, it is simply worthless.
      See http://bugs.python.org/issue21955 and
      http://bugs.python.org/issue10044 for the discussion. In short,
      no patch shown any impact on a realistic benchmark, only a minor
      speedup on microbenchmarks. */
   // 关于 string 的优化
   if (PyUnicode_CheckExact(left) &amp;&amp;
            PyUnicode_CheckExact(right)) {
         sum = unicode_concatenate(left, right, f, next_instr);
         /* unicode_concatenate consumed the ref to left */
   }
   else {
         sum = PyNumber_Add(left, right);
         Py_DECREF(left);
   }
   Py_DECREF(right);
   SET_TOP(sum);
   if (sum == NULL)
         goto error;
   DISPATCH();
}

// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1648
case TARGET(RETURN_VALUE): {
   retval = POP();
   assert(f-&gt;f_iblock == 0);
   goto return_or_yield;
}`, `70360662633240986000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1070&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LOAD_FAST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GETLOCAL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oparg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;format_exc_check_arg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tstate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyExc_UnboundLocalError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                              UNBOUNDLOCAL_ERROR_MSG&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                              &lt;span class=&quot;token function&quot;&gt;PyTuple_GetItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;co&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;co_varnames&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oparg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;Py_INCREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 引用&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;PUSH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;FAST_DISPATCH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 进入下个循环&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1280&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BINARY_ADD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;POP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;sum&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* NOTE(haypo): Please don&apos;t try to micro-optimize int+int on
      CPython using bytecode, it is simply worthless.
      See http://bugs.python.org/issue21955 and
      http://bugs.python.org/issue10044 for the discussion. In short,
      no patch shown any impact on a realistic benchmark, only a minor
      speedup on microbenchmarks. */&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 关于 string 的优化&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyUnicode_CheckExact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;PyUnicode_CheckExact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         sum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unicode_concatenate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next_instr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;/* unicode_concatenate consumed the ref to left */&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         sum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyNumber_Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;SET_TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;DISPATCH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1648&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;RETURN_VALUE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   retval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;POP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;f_iblock &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; return_or_yield&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;code-object&quot;&gt;&lt;a href=&quot;#code-object&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Code Object&lt;/h1&gt;
&lt;p&gt;编译一次就不会再改变&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;37666183135466970000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def f(a, *, b=3, **kwargs):
    pass


code = f.__code__

# 代码位置
print(f&amp;quot;co_code: {code.co_code}&amp;quot;)  # 这段代码真正的 bytecode，用二进制表示
print(f&amp;quot;co_filename: {code.co_filename}&amp;quot;)  # 这段 code 的名字，在哪一个文件里被定义的
print(
    f&amp;quot;co_lnotab: {code.co_lnotab}&amp;quot;
)  # mapping，bytecode 对应的源代码行数，用二进制表示

# virtual machine 需要的数据
print(
    f&amp;quot;co_flags: {code.co_flags}&amp;quot;
)  # bitmap，编译时会判断 code 有没有特别属性根据这些来执行不同的逻辑，比如有没有 *args/**kwargs/generator/coroutine
print(f&amp;quot;co_stacksize: {code.co_stacksize}&amp;quot;)  # virtual machine 需要栈空间有多大

# 输入参数数量，python 函数重载基础
print(f&amp;quot;co_argcount: {code.co_argcount}&amp;quot;)  # 排除 * 和 ** 之后的数量
print(
    f&amp;quot;co_posonlyargcount: {code.co_posonlyargcount}&amp;quot;
)  # / 之前的所有参数都是 args 变量
print(
    f&amp;quot;co_kwonlyargcount: {code.co_kwonlyargcount}&amp;quot;
)  # * 之后的所有参数都是 kwargs 变量

f(1)
f(1, b=1)
f(a=1)


print(f&amp;quot;co_nlocals: {code.co_nlocals}&amp;quot;)  # 局部变量数量

print(f&amp;quot;co_varnames: {code.co_varnames}&amp;quot;)  # 局部变量
print(f&amp;quot;co_names: {code.co_names}&amp;quot;)  # 除了 varnames/cellvars/freevars 之外的变量
print(f&amp;quot;co_cellvars: {code.co_cellvars}&amp;quot;)  # 其他的 scope 也会用，一般用来实现闭包方式
print(f&amp;quot;co_freevars: {code.co_freevars}&amp;quot;)  # 这个 vars 是从其他 scope 中来的

print(f&amp;quot;co_consts: {code.co_consts}&amp;quot;)  # 函数中出现的常量`, `37666183135466970000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;


code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__code__

&lt;span class=&quot;token comment&quot;&gt;# 代码位置&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_code: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_code&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 这段代码真正的 bytecode，用二进制表示&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_filename: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_filename&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 这段 code 的名字，在哪一个文件里被定义的&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_lnotab: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_lnotab&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# mapping，bytecode 对应的源代码行数，用二进制表示&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# virtual machine 需要的数据&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_flags: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_flags&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# bitmap，编译时会判断 code 有没有特别属性根据这些来执行不同的逻辑，比如有没有 *args/**kwargs/generator/coroutine&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_stacksize: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_stacksize&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# virtual machine 需要栈空间有多大&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 输入参数数量，python 函数重载基础&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_argcount: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_argcount&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 排除 * 和 ** 之后的数量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_posonlyargcount: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_posonlyargcount&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# / 之前的所有参数都是 args 变量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_kwonlyargcount: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_kwonlyargcount&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# * 之后的所有参数都是 kwargs 变量&lt;/span&gt;

f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_nlocals: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_nlocals&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 局部变量数量&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_varnames: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_varnames&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 局部变量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_names: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_names&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 除了 varnames/cellvars/freevars 之外的变量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_cellvars: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_cellvars&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 其他的 scope 也会用，一般用来实现闭包方式&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_freevars: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_freevars&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 这个 vars 是从其他 scope 中来的&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;co_consts: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_consts&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 函数中出现的常量&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40930268637742850000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`co_code: b&apos;d\x00S\x00&apos;
co_filename: /blog/Python深入学习/test.py
co_lnotab: b&apos;\x00\x01&apos;
co_flags: 75
co_stacksize: 1
co_argcount: 1
co_posonlyargcount: 0
co_kwonlyargcount: 1
co_nlocals: 3
co_varnames: (&apos;a&apos;, &apos;b&apos;, &apos;kwargs&apos;)
co_names: ()
co_cellvars: ()
co_freevars: ()
co_consts: (None,)`, `40930268637742850000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;co_code: b&lt;span class=&quot;token string&quot;&gt;&apos;d&lt;span class=&quot;token entity&quot; title=&quot;\x00&quot;&gt;\x00&lt;/span&gt;S&lt;span class=&quot;token entity&quot; title=&quot;\x00&quot;&gt;\x00&lt;/span&gt;&apos;&lt;/span&gt;
co_filename: /blog/Python深入学习/test.py
co_lnotab: b&lt;span class=&quot;token string&quot;&gt;&apos;&lt;span class=&quot;token entity&quot; title=&quot;\x00&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;\x01&quot;&gt;\x01&lt;/span&gt;&apos;&lt;/span&gt;
co_flags: &lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;
co_stacksize: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
co_argcount: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
co_posonlyargcount: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
co_kwonlyargcount: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
co_nlocals: &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
co_varnames: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;a&apos;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;b&apos;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;kwargs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
co_names: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
co_cellvars: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
co_freevars: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
co_consts: &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;None,&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;frame&quot;&gt;&lt;a href=&quot;#frame&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Frame&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;和 Code Object 不同是动态的，记录当前运行时状态&lt;/li&gt;
&lt;li&gt;每个函数只会编译出来一个 code object，大部分信息是保存在 code object 里面的，而 frame 根据执行逻辑不同，可能会对同一函数有不同的 frame&lt;/li&gt;
&lt;li&gt;每一次函数的执行开始到结束可以认为是当前 frame 的开始结束，结束时返回上一个 frame（栈结构）&lt;/li&gt;
&lt;/ol&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72956512113730730000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import inspect

# import sys
from objprint import op


def f():
   frame = inspect.currentframe()
   # frame = sys._getframe()
   op(frame, honor_existing=False, depth=1)


f()`, `72956512113730730000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; inspect

&lt;span class=&quot;token comment&quot;&gt;# import sys&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; objprint &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; op


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inspect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentframe&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;# frame = sys._getframe()&lt;/span&gt;
   op&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; honor_existing&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; depth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;21486903436189930000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;frame 0x7f7064b28400
  .f_back = &lt;frame 0x560f77c639c0 ... &gt;, # 上一个 frame
  .f_builtins = { ... }, # 这个函数下的内置函数
  .f_code = &lt;code 0x7f7064e6f500 ... &gt;, # 代码运行信息
  .f_globals = { ... }, # 全局变量
  .f_lasti = 18, # 运行到哪个字节码
  .f_lineno = 10, # 运行到第几行
  .f_locals = { ... }, # 局部变量，实质上是一个读出来的值，用了类似数组的方式保存的
  .f_trace = None, # 下面 3 个变量和 trace 有关，比如 debugger/coverage，sys.settrace 设置后就不是 None
  .f_trace_lines = True, # 是否每一行触发函数
  .f_trace_opcodes = False # 是否每一个字节码触发函数
&gt;`, `21486903436189930000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;frame 0x7f7064b28400
  .f_back &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;frame 0x560f77c639c0 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 上一个 frame&lt;/span&gt;
  .f_builtins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 这个函数下的内置函数&lt;/span&gt;
  .f_code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;code 0x7f7064e6f500 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 代码运行信息&lt;/span&gt;
  .f_globals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 全局变量&lt;/span&gt;
  .f_lasti &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 运行到哪个字节码&lt;/span&gt;
  .f_lineno &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 运行到第几行&lt;/span&gt;
  .f_locals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;, &lt;span class=&quot;token comment&quot;&gt;# 局部变量，实质上是一个读出来的值，用了类似数组的方式保存的&lt;/span&gt;
  .f_trace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; None, &lt;span class=&quot;token comment&quot;&gt;# 下面 3 个变量和 trace 有关，比如 debugger/coverage，sys.settrace 设置后就不是 None&lt;/span&gt;
  .f_trace_lines &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; True, &lt;span class=&quot;token comment&quot;&gt;# 是否每一行触发函数&lt;/span&gt;
  .f_trace_opcodes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; False &lt;span class=&quot;token comment&quot;&gt;# 是否每一个字节码触发函数&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53372121230565850000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import inspect


def f():
   frame = inspect.currentframe()

   # 调用方函数的情况
   print(frame.f_back.f_code.co_name)
   print(frame.f_back.f_locals)

   # 需要知道是调用了这个函数
   print(frame.f_back.f_code.co_filename)
   print(frame.f_back.f_lineno)


def g():
   a = 3
   b = 4
   f()


g()`, `53372121230565850000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; inspect


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inspect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentframe&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;# 调用方函数的情况&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_back&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_back&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_locals&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;# 需要知道是调用了这个函数&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_back&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_code&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;co_filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_back&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;f_lineno&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
   b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
   f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;16459522896966728000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`g
{&apos;a&apos;: 3, &apos;b&apos;: 4}
路径/test4.py
20`, `16459522896966728000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;g
{&amp;#39;a&amp;#39;: 3, &amp;#39;b&amp;#39;: 4}
路径/test4.py
20&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;binary_add&quot;&gt;&lt;a href=&quot;#binary_add&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;BINARY_ADD&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;99202384135808220000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1280
case TARGET(BINARY_ADD): {
   PyObject *right = POP();
   PyObject *left = TOP();
   PyObject *sum;
   /* NOTE(haypo): Please don&apos;t try to micro-optimize int+int on
      CPython using bytecode, it is simply worthless.
      See http://bugs.python.org/issue21955 and
      http://bugs.python.org/issue10044 for the discussion. In short,
      no patch shown any impact on a realistic benchmark, only a minor
      speedup on microbenchmarks. */
   // 关于 string 的优化
   if (PyUnicode_CheckExact(left) &amp;&amp;
            PyUnicode_CheckExact(right)) {
         sum = unicode_concatenate(left, right, f, next_instr);
         /* unicode_concatenate consumed the ref to left */
   }
   else {
         // 耗时最长
         sum = PyNumber_Add(left, right);
         Py_DECREF(left);
   }
   Py_DECREF(right);
   SET_TOP(sum);
   if (sum == NULL)
         goto error;
   DISPATCH();
}

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/abstract.c#L956
PyObject *
PyNumber_Add(PyObject *v, PyObject *w)
{
   PyObject *result = binary_op1(v, w, NB_SLOT(nb_add));
   if (result == Py_NotImplemented) {
      PySequenceMethods *m = v-&gt;ob_type-&gt;tp_as_sequence;
      Py_DECREF(result);
      if (m &amp;&amp; m-&gt;sq_concat) {
         return (*m-&gt;sq_concat)(v, w);
      }
      result = binop_type_error(v, w, &amp;quot;+&amp;quot;);
   }
   return result;
}

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/abstract.c#L785
static PyObject *
binary_op1(PyObject *v, PyObject *w, const int op_slot)
{
   PyObject *x;
   binaryfunc slotv = NULL;
   binaryfunc slotw = NULL;

   // 左边是数字，开始加
   if (v-&gt;ob_type-&gt;tp_as_number != NULL)
      slotv = NB_BINOP(v-&gt;ob_type-&gt;tp_as_number, op_slot);
   // 左右两边类型不同且右边是数字
   if (w-&gt;ob_type != v-&gt;ob_type &amp;&amp;
      w-&gt;ob_type-&gt;tp_as_number != NULL) {
      slotw = NB_BINOP(w-&gt;ob_type-&gt;tp_as_number, op_slot);
      if (slotw == slotv)
         slotw = NULL;
   }
   if (slotv) {
      // v/w 是同一类型，且 v/w 都有 + 函数指针，比如 1 + 1
      if (slotw &amp;&amp; PyType_IsSubtype(w-&gt;ob_type, v-&gt;ob_type)) {
         x = slotw(v, w);
         if (x != Py_NotImplemented)
               return x;
         Py_DECREF(x); /* can&apos;t do it */
         slotw = NULL;
      }
      // 调用 long_add
      x = slotv(v, w);
      if (x != Py_NotImplemented)
         return x;
      Py_DECREF(x); /* can&apos;t do it */
   }
   if (slotw) {
      x = slotw(v, w);
      if (x != Py_NotImplemented)
         return x;
      Py_DECREF(x); /* can&apos;t do it */
   }
   Py_RETURN_NOTIMPLEMENTED;
}

static PyObject *
long_add(PyLongObject *a, PyLongObject *b)
{
   // a, b 能不能做加法
   CHECK_BINOP(a, b);
   return _PyLong_Add(a, b);
}

PyObject *
_PyLong_Add(PyLongObject *a, PyLongObject *b)
{
   // a, b 都比较小的时候，可以加
   if (_PyLong_BothAreCompact(a, b)) {
      // 耗时部分
      return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));
   }

   // 如果 a, b 很大的情况
   PyLongObject *z;
   if (_PyLong_IsNegative(a)) {
      if (_PyLong_IsNegative(b)) {
         z = x_add(a, b);
         if (z != NULL) {
               /* x_add received at least one multiple-digit int,
                  and thus z must be a multiple-digit int.
                  That also means z is not an element of
                  small_ints, so negating it in-place is safe. */
               assert(Py_REFCNT(z) == 1);
               _PyLong_FlipSign(z);
         }
      }
      else
         z = x_sub(b, a);
   }
   else {
      if (_PyLong_IsNegative(b))
         z = x_sub(a, b);
      else
         z = x_add(a, b);
   }
   return (PyObject *)z;
}

/* Create a new int object from a C word-sized int */
static inline PyObject *
_PyLong_FromSTwoDigits(stwodigits x)
{
   if (IS_SMALL_INT(x)) {
      return get_small_int((sdigit)x);
   }
   assert(x != 0);
   if (is_medium_int(x)) {
      return _PyLong_FromMedium((sdigit)x);
   }
   return _PyLong_FromLarge(x);
}

static PyObject *
get_small_int(sdigit ival)
{
   assert(IS_SMALL_INT(ival));
   return (PyObject *)&amp;_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + ival];
}

static PyObject *
_PyLong_FromMedium(sdigit x)
{
   assert(!IS_SMALL_INT(x));
   assert(is_medium_int(x));
   /* We could use a freelist here */
   PyLongObject *v = PyObject_Malloc(sizeof(PyLongObject));
   if (v == NULL) {
      PyErr_NoMemory();
      return NULL;
   }
   digit abs_x = x &lt; 0 ? -x : x;
   _PyLong_SetSignAndDigitCount(v, x&lt;0?-1:1, 1);
   _PyObject_Init((PyObject*)v, &amp;PyLong_Type);
   v-&gt;long_value.ob_digit[0] = abs_x;
   return (PyObject*)v;
}

static PyObject *
_PyLong_FromLarge(stwodigits ival)
{
   twodigits abs_ival;
   int sign;
   assert(!is_medium_int(ival));

   if (ival &lt; 0) {
      /* negate: can&apos;t write this as abs_ival = -ival since that
         invokes undefined behaviour when ival is LONG_MIN */
      abs_ival = 0U-(twodigits)ival;
      sign = -1;
   }
   else {
      abs_ival = (twodigits)ival;
      sign = 1;
   }
   /* Must be at least two digits */
   assert(abs_ival &gt;&gt; PyLong_SHIFT != 0);
   twodigits t = abs_ival &gt;&gt; (PyLong_SHIFT * 2);
   Py_ssize_t ndigits = 2;
   while (t) {
      ++ndigits;
      t &gt;&gt;= PyLong_SHIFT;
   }
   PyLongObject *v = _PyLong_New(ndigits);
   if (v != NULL) {
      digit *p = v-&gt;long_value.ob_digit;
      _PyLong_SetSignAndDigitCount(v, sign, ndigits);
      t = abs_ival;
      while (t) {
         *p++ = Py_SAFE_DOWNCAST(
               t &amp; PyLong_MASK, twodigits, digit);
         t &gt;&gt;= PyLong_SHIFT;
      }
   }
   return (PyObject *)v;
}

PyLongObject *
_PyLong_New(Py_ssize_t size)
{
   assert(size &gt;= 0);
   PyLongObject *result;
   if (size &gt; (Py_ssize_t)MAX_LONG_DIGITS) {
      PyErr_SetString(PyExc_OverflowError,
                     &amp;quot;too many digits in integer&amp;quot;);
      return NULL;
   }
   /* Fast operations for single digit integers (including zero)
   * assume that there is always at least one digit present. */
   Py_ssize_t ndigits = size ? size : 1;
   /* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
      sizeof(digit)*size.  Previous incarnations of this code used
      sizeof() instead of the offsetof, but this risks being
      incorrect in the presence of padding between the header
      and the digits. */
   // 申请新内存，耗时核心原因
   result = PyObject_Malloc(offsetof(PyLongObject, long_value.ob_digit) +
                           ndigits*sizeof(digit));
   if (!result) {
      PyErr_NoMemory();
      return NULL;
   }
   _PyLong_SetSignAndDigitCount(result, size != 0, size);
   _PyObject_Init((PyObject*)result, &amp;PyLong_Type);
   /* The digit has to be initialized explicitly to avoid
   * use-of-uninitialized-value. */
   result-&gt;long_value.ob_digit[0] = 0;
   return result;
}`, `99202384135808220000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/ddd1949fea59f256e51191540a4446f75ed608fa/Python/ceval.c#L1280&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BINARY_ADD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;POP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;sum&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* NOTE(haypo): Please don&apos;t try to micro-optimize int+int on
      CPython using bytecode, it is simply worthless.
      See http://bugs.python.org/issue21955 and
      http://bugs.python.org/issue10044 for the discussion. In short,
      no patch shown any impact on a realistic benchmark, only a minor
      speedup on microbenchmarks. */&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 关于 string 的优化&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyUnicode_CheckExact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;PyUnicode_CheckExact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         sum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unicode_concatenate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next_instr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;/* unicode_concatenate consumed the ref to left */&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 耗时最长&lt;/span&gt;
         sum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyNumber_Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;SET_TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;DISPATCH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/abstract.c#L956&lt;/span&gt;
PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;PyNumber_Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;binary_op1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NB_SLOT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nb_add&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; Py_NotImplemented&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      PySequenceMethods &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_as_sequence&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; m&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;sq_concat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;m&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;sq_concat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;binop_type_error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/abstract.c#L785&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;binary_op1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; op_slot&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   binaryfunc slotv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   binaryfunc slotw &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 左边是数字，开始加&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_as_number &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      slotv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NB_BINOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_as_number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; op_slot&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 左右两边类型不同且右边是数字&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      w&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_as_number &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      slotw &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NB_BINOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_as_number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; op_slot&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slotw &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; slotv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         slotw &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slotv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// v/w 是同一类型，且 v/w 都有 + 函数指针，比如 1 + 1&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slotw &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyType_IsSubtype&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slotw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; Py_NotImplemented&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* can&apos;t do it */&lt;/span&gt;
         slotw &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 调用 long_add&lt;/span&gt;
      x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slotv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; Py_NotImplemented&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* can&apos;t do it */&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slotw&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slotw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; Py_NotImplemented&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* can&apos;t do it */&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   Py_RETURN_NOTIMPLEMENTED&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;long_add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// a, b 能不能做加法&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;CHECK_BINOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyLong_Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyLong_Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// a, b 都比较小的时候，可以加&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyLong_BothAreCompact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 耗时部分&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyLong_FromSTwoDigits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;medium_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;medium_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果 a, b 很大的情况&lt;/span&gt;
   PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;z&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyLong_IsNegative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyLong_IsNegative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         z &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;x_add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;z &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token comment&quot;&gt;/* x_add received at least one multiple-digit int,
                  and thus z must be a multiple-digit int.
                  That also means z is not an element of
                  small_ints, so negating it in-place is safe. */&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Py_REFCNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;z&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;_PyLong_FlipSign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;z&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
         z &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;x_sub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyLong_IsNegative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         z &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;x_sub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
         z &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;x_add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;z&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Create a new int object from a C word-sized int */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;inline&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyLong_FromSTwoDigits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stwodigits x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IS_SMALL_INT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_small_int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sdigit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_medium_int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyLong_FromMedium&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sdigit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyLong_FromLarge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;get_small_int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sdigit ival&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IS_SMALL_INT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ival&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;_PyLong_SMALL_INTS&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_PY_NSMALLNEGINTS &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ival&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyLong_FromMedium&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sdigit x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IS_SMALL_INT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_medium_int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* We could use a freelist here */&lt;/span&gt;
   PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyObject_Malloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyLongObject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_NoMemory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   digit abs_x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;_PyLong_SetSignAndDigitCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;_PyObject_Init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;PyLong_Type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;long_value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ob_digit&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; abs_x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyLong_FromLarge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stwodigits ival&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   twodigits abs_ival&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sign&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_medium_int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ival&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ival &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;/* negate: can&apos;t write this as abs_ival = -ival since that
         invokes undefined behaviour when ival is LONG_MIN */&lt;/span&gt;
      abs_ival &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0U&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;twodigits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;ival&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      sign &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      abs_ival &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;twodigits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;ival&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      sign &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* Must be at least two digits */&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;abs_ival &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; PyLong_SHIFT &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   twodigits t &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; abs_ival &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyLong_SHIFT &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   Py_ssize_t ndigits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;ndigits&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      t &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;=&lt;/span&gt; PyLong_SHIFT&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyLong_New&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ndigits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      digit &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;p &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; v&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;long_value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ob_digit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;_PyLong_SetSignAndDigitCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sign&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ndigits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      t &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; abs_ival&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Py_SAFE_DOWNCAST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
               t &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; PyLong_MASK&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; twodigits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; digit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         t &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;=&lt;/span&gt; PyLong_SHIFT&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyLong_New&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Py_ssize_t size&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyLongObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Py_ssize_t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;MAX_LONG_DIGITS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_SetString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_OverflowError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                     &lt;span class=&quot;token string&quot;&gt;&quot;too many digits in integer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* Fast operations for single digit integers (including zero)
   * assume that there is always at least one digit present. */&lt;/span&gt;
   Py_ssize_t ndigits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
      sizeof(digit)*size.  Previous incarnations of this code used
      sizeof() instead of the offsetof, but this risks being
      incorrect in the presence of padding between the header
      and the digits. */&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 申请新内存，耗时核心原因&lt;/span&gt;
   result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyObject_Malloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offsetof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyLongObject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; long_value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ob_digit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
                           ndigits&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;digit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_NoMemory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;_PyLong_SetSignAndDigitCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;_PyObject_Init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;PyLong_Type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* The digit has to be initialized explicitly to avoid
   * use-of-uninitialized-value. */&lt;/span&gt;
   result&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;long_value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ob_digit&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;综上，python 做一个简单加法这么复杂的原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;灵活代价，复杂度交给了底层性能不高&lt;/li&gt;
&lt;li&gt;建立对象消耗性能&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;gil&quot;&gt;&lt;a href=&quot;#gil&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Gil&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30019551998759320000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// https://github.com/python/cpython/blob/e225bebc1409bcf68db74a35ed3c31222883bf8f/Python/ceval.c#L1234

if (_Py_atomic_load_relaxed(&amp;ceval-&gt;gil_drop_request)) {
   /* Give another thread a chance */
   if (_PyThreadState_Swap(&amp;runtime-&gt;gilstate, NULL) != tstate) {
      Py_FatalError(&amp;quot;ceval: tstate mix-up&amp;quot;);
   }
   drop_gil(ceval, tstate);

   /* Other threads may run now */

   take_gil(ceval, tstate);

   /* Check if we should make a quick exit. */
   exit_thread_if_finalizing(tstate);

   if (_PyThreadState_Swap(&amp;runtime-&gt;gilstate, tstate) != NULL) {
      Py_FatalError(&amp;quot;ceval: orphan tstate&amp;quot;);
   }
}`, `30019551998759320000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/e225bebc1409bcf68db74a35ed3c31222883bf8f/Python/ceval.c#L1234&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_Py_atomic_load_relaxed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;ceval&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;gil_drop_request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* Give another thread a chance */&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyThreadState_Swap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;runtime&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;gilstate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; tstate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_FatalError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ceval: tstate mix-up&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;drop_gil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ceval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tstate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;/* Other threads may run now */&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;take_gil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ceval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tstate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;/* Check if we should make a quick exit. */&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;exit_thread_if_finalizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tstate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_PyThreadState_Swap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;runtime&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;gilstate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tstate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_FatalError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ceval: orphan tstate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以上代码保证了当前线程会拿到全局的 gil 锁，通过这种机制可以保证每个 bytecode 在运行时会拿到全局锁，不会被其他线程打断&lt;/p&gt;
&lt;p&gt;有了这个全局锁之后好处是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;简单的设计不会出错：比每个 object 都实现一个锁要简单&lt;/li&gt;
&lt;li&gt;由于只有一个线程锁，避免了死锁问题&lt;/li&gt;
&lt;li&gt;对于单线程或没法并行的多线程程序，全局锁性能优秀，如果每个 object 都实现一个锁，那么由于每一行 bytecode 要读写很多 object，需要加很多锁性能较低&lt;/li&gt;
&lt;li&gt;cpython 维护简单，不需要在修改 python object 的时候关心锁问题，让第三方开发者心智负担降低&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然有做过拿掉 gil 的尝试，但是有以下问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不能保证单进程、单线程下运行速度&lt;/li&gt;
&lt;li&gt;向后兼容问题，不能兼容之前的代码&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;坏处是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;python 多线程无法利用多核增加运行速度，但是可以用以下方式解决&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多进程&lt;/li&gt;
&lt;li&gt;c 拓展在 c 里面做多线程&lt;/li&gt;
&lt;li&gt;无 gil 的 python 解释器&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;descriptor&quot;&gt;&lt;a href=&quot;#descriptor&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Descriptor&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;44000129541397120000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# 情况 1
class Name:
    def __get__(self, obj, objtype):
        return &amp;quot;Peter&amp;quot;


class A:
    name = Name()


print(A.name) # Peter


# 情况 2
class Name:
    def __get__(self, obj, objtype):
        return &amp;quot;Peter&amp;quot;


class A:
    def __init__(self):
        self.name = Name()


o = A()
print(o.name) # &lt;__main__.Name object at 0x7fb1658443a0&gt;


# 情况 3
class Name:
    def __get__(self, obj, objtype):
        return &amp;quot;Peter&amp;quot;


class A:
    name = Name()


o = A()
o.name = &amp;quot;Bob&amp;quot;
print(o.name) # Bob


# 情况 4
class Name:
    def __get__(self, obj, objtype):
        return &amp;quot;Peter&amp;quot;


class A:
    name = Name()


o = A()
o.name = &amp;quot;Bob&amp;quot;
Name.__set__ = lambda x, y, z: None
print(o.name) # Peter`, `44000129541397120000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 情况 1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; objtype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Peter&quot;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;A&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Peter&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# 情况 2&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; objtype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Peter&quot;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; A&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# &amp;lt;__main__.Name object at 0x7fb1658443a0&gt;&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# 情况 3&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; objtype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Peter&quot;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; A&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Bob&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# 情况 4&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__get__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; objtype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Peter&quot;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; A&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;
Name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__set__ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Peter&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本质上和字节码 &lt;code class=&quot;language-text&quot;&gt;LOAD_ATTR&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;STORE_ATTR&lt;/code&gt; 有关，有着相似的逻辑，下面只说明 &lt;code class=&quot;language-text&quot;&gt;LOAD_ATTR&lt;/code&gt; 的情况&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17395627160884386000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Python/ceval.c#L2963
case TARGET(LOAD_ATTR): {
   PyObject *name = GETITEM(names, oparg);
   PyObject *owner = TOP();
   // 核心逻辑
   PyObject *res = PyObject_GetAttr(owner, name);
   Py_DECREF(owner);
   SET_TOP(res);
   if (res == NULL)
         goto error;
   DISPATCH();
}

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L929
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
   PyTypeObject *tp = Py_TYPE(v);

   if (!PyUnicode_Check(name)) {
      PyErr_Format(PyExc_TypeError,
                  &amp;quot;attribute name must be string, not &apos;%.200s&apos;&amp;quot;,
                  name-&gt;ob_type-&gt;tp_name);
      return NULL;
   }
   if (tp-&gt;tp_getattro != NULL)
      // 核心逻辑 getattro
      return (*tp-&gt;tp_getattro)(v, name);
   if (tp-&gt;tp_getattr != NULL) {
      const char *name_str = PyUnicode_AsUTF8(name);
      if (name_str == NULL)
         return NULL;
      return (*tp-&gt;tp_getattr)(v, (char *)name_str);
   }
   PyErr_Format(PyExc_AttributeError,
               &amp;quot;&apos;%.50s&apos; object has no attribute &apos;%U&apos;&amp;quot;,
               tp-&gt;tp_name, name);
   return NULL;
}

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/typeobject.c#L4796
PyTypeObject PyBaseObject_Type = {
   PyVarObject_HEAD_INIT(&amp;PyType_Type, 0)
   &amp;quot;object&amp;quot;,                                   /* tp_name */
   sizeof(PyObject),                           /* tp_basicsize */
   0,                                          /* tp_itemsize */
   object_dealloc,                             /* tp_dealloc */
   0,                                          /* tp_vectorcall_offset */
   0,                                          /* tp_getattr */
   0,                                          /* tp_setattr */
   0,                                          /* tp_as_async */
   object_repr,                                /* tp_repr */
   0,                                          /* tp_as_number */
   0,                                          /* tp_as_sequence */
   0,                                          /* tp_as_mapping */
   (hashfunc)_Py_HashPointer,                  /* tp_hash */
   0,                                          /* tp_call */
   object_str,                                 /* tp_str */
   // 没有重载的时候用的逻辑
   PyObject_GenericGetAttr,                    /* tp_getattro */
   PyObject_GenericSetAttr,                    /* tp_setattro */
   0,                                          /* tp_as_buffer */
   Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
   object_doc,                                 /* tp_doc */
   0,                                          /* tp_traverse */
   0,                                          /* tp_clear */
   object_richcompare,                         /* tp_richcompare */
   0,                                          /* tp_weaklistoffset */
   0,                                          /* tp_iter */
   0,                                          /* tp_iternext */
   object_methods,                             /* tp_methods */
   0,                                          /* tp_members */
   object_getsets,                             /* tp_getset */
   0,                                          /* tp_base */
   0,                                          /* tp_dict */
   0,                                          /* tp_descr_get */
   0,                                          /* tp_descr_set */
   0,                                          /* tp_dictoffset */
   object_init,                                /* tp_init */
   PyType_GenericAlloc,                        /* tp_alloc */
   object_new,                                 /* tp_new */
   PyObject_Del,                               /* tp_free */
};

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L1332C1-L1336C2
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
    return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);
}

// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L1217
// obj: obj 本身，name: attr 值，字符串
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
                                 PyObject *dict, int suppress)
{
   /* Make sure the logic of _PyObject_GetMethod is in sync with
      this method.

      When suppress=1, this function suppress AttributeError.
   */
   // obj 的 type 就是 class A
   PyTypeObject *tp = Py_TYPE(obj);
   PyObject *descr = NULL;
   PyObject *res = NULL;
   descrgetfunc f;
   Py_ssize_t dictoffset;
   PyObject **dictptr;

   if (!PyUnicode_Check(name)){
      PyErr_Format(PyExc_TypeError,
                  &amp;quot;attribute name must be string, not &apos;%.200s&apos;&amp;quot;,
                  name-&gt;ob_type-&gt;tp_name);
      return NULL;
   }
   Py_INCREF(name);

   if (tp-&gt;tp_dict == NULL) {
      if (PyType_Ready(tp) &lt; 0)
         goto done;
   }

   // 在这个 type 里找 name，也就是说 descriptor 必须定义在 type 上，即写到 class 的定义里面，解释了情况 2
   descr = _PyType_Lookup(tp, name);

   f = NULL;
   if (descr != NULL) {
      Py_INCREF(descr);
      // 首先寻找了可能是 descriptor 的 object 有没有 tp_descr_get 函数，即有没有 __get__ 函数
      f = descr-&gt;ob_type-&gt;tp_descr_get;
      // 判断 IsData：实际上就是有没有 __set__ 函数
      if (f != NULL &amp;&amp; PyDescr_IsData(descr)) {
         // 返回 __get__ 函数的返回值
         res = f(descr, obj, (PyObject *)obj-&gt;ob_type);
         if (res == NULL &amp;&amp; suppress &amp;&amp;
                  PyErr_ExceptionMatches(PyExc_AttributeError)) {
               PyErr_Clear();
         }
         goto done;
      }
   }

   // 找到 object 本身的 __dict__，里面保存了所有的成员变量
   if (dict == NULL) {
      /* Inline _PyObject_GetDictPtr */
      dictoffset = tp-&gt;tp_dictoffset;
      if (dictoffset != 0) {
         if (dictoffset &lt; 0) {
               Py_ssize_t tsize;
               size_t size;

               tsize = ((PyVarObject *)obj)-&gt;ob_size;
               if (tsize &lt; 0)
                  tsize = -tsize;
               size = _PyObject_VAR_SIZE(tp, tsize);
               _PyObject_ASSERT(obj, size &lt;= PY_SSIZE_T_MAX);

               dictoffset += (Py_ssize_t)size;
               _PyObject_ASSERT(obj, dictoffset &gt; 0);
               _PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
         }
         dictptr = (PyObject **) ((char *)obj + dictoffset);
         dict = *dictptr;
      }
   }
   // object 的 key 里面找对应的值，和 object 绑定的变量
   if (dict != NULL) {
      Py_INCREF(dict);
      res = PyDict_GetItemWithError(dict, name);
      if (res != NULL) {
         Py_INCREF(res);
         Py_DECREF(dict);
         goto done;
      }
      else {
         Py_DECREF(dict);
         if (PyErr_Occurred()) {
               if (suppress &amp;&amp; PyErr_ExceptionMatches(PyExc_AttributeError)) {
                  PyErr_Clear();
               }
               else {
                  goto done;
               }
         }
      }
   }

   // 如果只有 __get__ 函数，没有 __set__ 函数
   if (f != NULL) {
      res = f(descr, obj, (PyObject *)Py_TYPE(obj));
      if (res == NULL &amp;&amp; suppress &amp;&amp;
               PyErr_ExceptionMatches(PyExc_AttributeError)) {
         PyErr_Clear();
      }
      goto done;
   }

   // 如果没有 __get__、__set__ 函数，原样返回
   // 对应如下代码
   // class A:
   //    name = &amp;quot;Alice&amp;quot;
   // a = A()
   // print(a.__dict__) # {}，没有值
   // print(a.name) # 没发现 name 有 __get__ 或 __set__ 函数，直接返回
   // 实现的是实例成员变量没有找到，再到类的属性上找，甚至到父类
   if (descr != NULL) {
      res = descr;
      descr = NULL;
      goto done;
   }

   if (!suppress) {
      PyErr_Format(PyExc_AttributeError,
                  &amp;quot;&apos;%.50s&apos; object has no attribute &apos;%U&apos;&amp;quot;,
                  tp-&gt;tp_name, name);
   }
  done:
    Py_XDECREF(descr);
    Py_DECREF(name);
    return res;
}`, `17395627160884386000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Python/ceval.c#L2963&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LOAD_ATTR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GETITEM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;names&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oparg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;owner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 核心逻辑&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyObject_GetAttr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;owner&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;owner&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;SET_TOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;DISPATCH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L929&lt;/span&gt;
PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;PyObject_GetAttr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   PyTypeObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;tp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Py_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyUnicode_Check&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_TypeError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;attribute name must be string, not &apos;%.200s&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  name&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_getattro &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 核心逻辑 getattro&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_getattro&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_getattr &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;name_str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyUnicode_AsUTF8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name_str &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_getattr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;name_str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;PyErr_Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_AttributeError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;token string&quot;&gt;&quot;&apos;%.50s&apos; object has no attribute &apos;%U&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/typeobject.c#L4796&lt;/span&gt;
PyTypeObject PyBaseObject_Type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;PyVarObject_HEAD_INIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;PyType_Type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                   &lt;span class=&quot;token comment&quot;&gt;/* tp_name */&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                           &lt;span class=&quot;token comment&quot;&gt;/* tp_basicsize */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_itemsize */&lt;/span&gt;
   object_dealloc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                             &lt;span class=&quot;token comment&quot;&gt;/* tp_dealloc */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_vectorcall_offset */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_getattr */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_setattr */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_as_async */&lt;/span&gt;
   object_repr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                &lt;span class=&quot;token comment&quot;&gt;/* tp_repr */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_as_number */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_as_sequence */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_as_mapping */&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hashfunc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;_Py_HashPointer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                  &lt;span class=&quot;token comment&quot;&gt;/* tp_hash */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_call */&lt;/span&gt;
   object_str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                 &lt;span class=&quot;token comment&quot;&gt;/* tp_str */&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 没有重载的时候用的逻辑&lt;/span&gt;
   PyObject_GenericGetAttr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                    &lt;span class=&quot;token comment&quot;&gt;/* tp_getattro */&lt;/span&gt;
   PyObject_GenericSetAttr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                    &lt;span class=&quot;token comment&quot;&gt;/* tp_setattro */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_as_buffer */&lt;/span&gt;
   Py_TPFLAGS_DEFAULT &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; Py_TPFLAGS_BASETYPE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;/* tp_flags */&lt;/span&gt;
   object_doc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                 &lt;span class=&quot;token comment&quot;&gt;/* tp_doc */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_traverse */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_clear */&lt;/span&gt;
   object_richcompare&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                         &lt;span class=&quot;token comment&quot;&gt;/* tp_richcompare */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_weaklistoffset */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_iter */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_iternext */&lt;/span&gt;
   object_methods&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                             &lt;span class=&quot;token comment&quot;&gt;/* tp_methods */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_members */&lt;/span&gt;
   object_getsets&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                             &lt;span class=&quot;token comment&quot;&gt;/* tp_getset */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_base */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_dict */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_descr_get */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_descr_set */&lt;/span&gt;
   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                          &lt;span class=&quot;token comment&quot;&gt;/* tp_dictoffset */&lt;/span&gt;
   object_init&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                &lt;span class=&quot;token comment&quot;&gt;/* tp_init */&lt;/span&gt;
   PyType_GenericAlloc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                        &lt;span class=&quot;token comment&quot;&gt;/* tp_alloc */&lt;/span&gt;
   object_new&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                                 &lt;span class=&quot;token comment&quot;&gt;/* tp_new */&lt;/span&gt;
   PyObject_Del&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;                               &lt;span class=&quot;token comment&quot;&gt;/* tp_free */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L1332C1-L1336C2&lt;/span&gt;
PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;PyObject_GenericGetAttr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyObject_GenericGetAttrWithDict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// https://github.com/python/cpython/blob/95c340a86b828cac6c0a2e4f2fa8a96695388c73/Objects/object.c#L1217&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// obj: obj 本身，name: attr 值，字符串&lt;/span&gt;
PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;_PyObject_GenericGetAttrWithDict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                 PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; suppress&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;/* Make sure the logic of _PyObject_GetMethod is in sync with
      this method.

      When suppress=1, this function suppress AttributeError.
   */&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// obj 的 type 就是 class A&lt;/span&gt;
   PyTypeObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;tp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Py_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;descr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   descrgetfunc f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   Py_ssize_t dictoffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;dictptr&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyUnicode_Check&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_TypeError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;attribute name must be string, not &apos;%.200s&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  name&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;Py_INCREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_dict &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyType_Ready&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 在这个 type 里找 name，也就是说 descriptor 必须定义在 type 上，即写到 class 的定义里面，解释了情况 2&lt;/span&gt;
   descr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyType_Lookup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_INCREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 首先寻找了可能是 descriptor 的 object 有没有 tp_descr_get 函数，即有没有 __get__ 函数&lt;/span&gt;
      f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; descr&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_descr_get&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 判断 IsData：实际上就是有没有 __set__ 函数&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyDescr_IsData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;// 返回 __get__ 函数的返回值&lt;/span&gt;
         res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;obj&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; suppress &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                  &lt;span class=&quot;token function&quot;&gt;PyErr_ExceptionMatches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_AttributeError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;PyErr_Clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 找到 object 本身的 __dict__，里面保存了所有的成员变量&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;/* Inline _PyObject_GetDictPtr */&lt;/span&gt;
      dictoffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_dictoffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dictoffset &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dictoffset &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               Py_ssize_t tsize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               size_t size&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

               tsize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyVarObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;ob_size&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tsize &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                  tsize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;tsize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyObject_VAR_SIZE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tsize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;_PyObject_ASSERT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; PY_SSIZE_T_MAX&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

               dictoffset &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Py_ssize_t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;_PyObject_ASSERT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dictoffset &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;_PyObject_ASSERT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dictoffset &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; SIZEOF_VOID_P &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         dictptr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;obj &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; dictoffset&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         dict &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;dictptr&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// object 的 key 里面找对应的值，和 object 绑定的变量&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;Py_INCREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyDict_GetItemWithError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_INCREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PyErr_Occurred&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suppress &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PyErr_ExceptionMatches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_AttributeError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token function&quot;&gt;PyErr_Clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果只有 __get__ 函数，没有 __set__ 函数&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Py_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; suppress &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;PyErr_ExceptionMatches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_AttributeError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;PyErr_Clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// 如果没有 __get__、__set__ 函数，原样返回&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 对应如下代码&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// class A:&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;//    name = &quot;Alice&quot;&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// a = A()&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// print(a.__dict__) # {}，没有值&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// print(a.name) # 没发现 name 有 __get__ 或 __set__ 函数，直接返回&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 实现的是实例成员变量没有找到，再到类的属性上找，甚至到父类&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; descr&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      descr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;suppress&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;PyErr_Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PyExc_AttributeError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;token string&quot;&gt;&quot;&apos;%.50s&apos; object has no attribute &apos;%U&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  tp&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;tp_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  done&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;Py_XDECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;descr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;总结，&lt;code class=&quot;language-text&quot;&gt;LOAD_ATTR&lt;/code&gt; 优先级如下，从大到小：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果 &lt;code class=&quot;language-text&quot;&gt;__get__&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;__set__&lt;/code&gt; 都有，用 &lt;code class=&quot;language-text&quot;&gt;__get__&lt;/code&gt; 返回值&lt;/li&gt;
&lt;li&gt;在 &lt;code class=&quot;language-text&quot;&gt;__dict__&lt;/code&gt; 找对应的值&lt;/li&gt;
&lt;li&gt;用 &lt;code class=&quot;language-text&quot;&gt;__get__&lt;/code&gt; 返回值&lt;/li&gt;
&lt;li&gt;类的属性&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;decorator&quot;&gt;&lt;a href=&quot;#decorator&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Decorator&lt;/h1&gt;
&lt;p&gt;从字节码来看，是一个输入输出都是函数的语法糖&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2536108007100868000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def dec(f):
    pass


@dec
def double(x):
    return x * 2


# 完全等价
double = dec(double)`, `2536108007100868000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;


@dec
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# 完全等价&lt;/span&gt;
double &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dec&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;double&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是输出不一定是函数&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;71630455874836940000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def dec(f):
    return 1


@dec
def double(x):
    return x * 2`, `71630455874836940000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;


@dec
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当然，装饰器还可以带参数&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92802413139978270000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import time


def timeit(iter):
    def inner(f):
        def wrapper(*args, **kwargs):
            start = time.time()
            for _ in range(iter):
                ret = f(*args, **kwargs)
            print(time.time() - start)
            return ret

        return wrapper

    return inner


@timeit(1000)
def double(x):
    return x * 2


# double(3)
# 等价于
inner = timeit(1000)
double = inner(double)`, `92802413139978270000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner


@timeit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# double(3)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 等价于&lt;/span&gt;
inner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timeit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
double &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inner&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;double&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;类装饰器&quot;&gt;&lt;a href=&quot;#%E7%B1%BB%E8%A3%85%E9%A5%B0%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;类装饰器&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19047295952582520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import time


class Timer:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start = time.time()
        ret = self.func(*args, **kwargs)
        print(f&amp;quot;Time: {time.time() - start}&amp;quot;)
        return ret


@Timer
def add(a, b):
    return a + b

# 等价于
# add = Timer(add)

print(add(2, 3))`, `19047295952582520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__call__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Time: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret


@Timer
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b

&lt;span class=&quot;token comment&quot;&gt;# 等价于&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# add = Timer(add)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;add&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;51601544238913390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import time


class Timer:
    def __init__(self, prefix):
        self.prefix = prefix

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            start = time.time()
            ret = func(*args, **kwargs)
            print(f&amp;quot;{self.prefix}{time.time() - start}&amp;quot;)
            return ret

        return wrapper


@Timer(prefix=&amp;quot;curr_time: &amp;quot;)
def add(a, b):
    return a + b


# 等价于
# add = Timer(prefix=&amp;quot;curr_time: &amp;quot;)(add)

print(add(2, 3))`, `51601544238913390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; time


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prefix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; prefix

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__call__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prefix&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper


@Timer&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefix&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;curr_time: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b


&lt;span class=&quot;token comment&quot;&gt;# 等价于&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# add = Timer(prefix=&quot;curr_time: &quot;)(add)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;add&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上面的 2 种都是装饰器类，下面是真正的类装饰器&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92594428215134390000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def add_str(cls):
    def __str__(self):
        return str(self.__dict__)

    cls.__str__ = __str__
    return cls


@add_str
class MyObject:
    def __init__(self, a, b):
        self.a = a
        self.b = b

# 等价于
# MyObject = add_str(MyObject)

o = MyObject(1, 2)
print(o)`, `92594428215134390000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add_str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__str__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__dict__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    cls&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__str__ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; __str__
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cls


@add_str
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b

&lt;span class=&quot;token comment&quot;&gt;# 等价于&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# MyObject = add_str(MyObject)&lt;/span&gt;

o &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MyObject&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;装饰器在类里面的调用方法&quot;&gt;&lt;a href=&quot;#%E8%A3%85%E9%A5%B0%E5%99%A8%E5%9C%A8%E7%B1%BB%E9%87%8C%E9%9D%A2%E7%9A%84%E8%B0%83%E7%94%A8%E6%96%B9%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;装饰器在类里面的调用方法&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31293427671267148000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def log_function(func):
    def wrapper(*args, **kwargs):
        print(f&amp;quot;function start!&amp;quot;)
        print(f&amp;quot;args: {args}&amp;quot;)
        ret = func(*args, **kwargs)
        print(f&amp;quot;function end!&amp;quot;)
        return ret

    return wrapper


@log_function
def fib(n):
    if n &lt;= 1:
        return 0
    return fib(n - 1) + fib(n - 2)


fib(3)`, `31293427671267148000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function start!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;args: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function end!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper


@log_function
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如何把全局的装饰器移到类里面？下面是一种可能的实现&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72157852572267545000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class Decorators:
    def log_function(self, func):
        def wrapper(*args, **kwargs):
            print(f&amp;quot;function start!&amp;quot;)
            print(f&amp;quot;args: {args}&amp;quot;)
            ret = func(*args, **kwargs)
            print(f&amp;quot;function end!&amp;quot;)
            return ret

        return wrapper


d = Decorators()


@d.log_function
def fib(n):
    if n &lt;= 1:
        return 0
    return fib(n - 1) + fib(n - 2)


fib(3)`, `72157852572267545000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Decorators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function start!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;args: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function end!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper


d &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Decorators&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


@d&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_function
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以上代码虽然可以，但是有以下缺点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为了使用这个装饰器，需要建立新对象（classmethod 解决，但还是会带有 self）&lt;/li&gt;
&lt;li&gt;装饰器出现 self 参数（staticmethod 解决，不会带 self）&lt;/li&gt;
&lt;/ol&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;81777955916573930000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class Decorators:
    @staticmethod
    def log_function(func):
        def wrapper(*args, **kwargs):
            print(f&amp;quot;function start!&amp;quot;)
            print(f&amp;quot;args: {args}&amp;quot;)
            ret = func(*args, **kwargs)
            print(f&amp;quot;function end!&amp;quot;)
            return ret

        return wrapper


@Decorators.log_function
def fib(n):
    if n &lt;= 1:
        return 0
    return fib(n - 1) + fib(n - 2)


fib(3)`, `81777955916573930000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Decorators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    @&lt;span class=&quot;token builtin&quot;&gt;staticmethod&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function start!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;args: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function end!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper


@Decorators&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_function
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果想用类里面的装饰器去装饰类里面的方法，可以直接去掉装饰器&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;19482838182610250000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class Decorators:
    def log_function(func):
        def wrapper(*args, **kwargs):
            print(f&amp;quot;function start!&amp;quot;)
            print(f&amp;quot;args: {args}&amp;quot;)
            ret = func(*args, **kwargs)
            print(f&amp;quot;function end!&amp;quot;)
            return ret

        return wrapper

    @log_function
    def fib(self, n):
        if n &lt;= 1:
            return 0
        return self.fib(n - 1) + self.fib(n - 2)


d = Decorators()
d.fib(3)`, `19482838182610250000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Decorators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function start!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;args: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function end!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper

    @log_function
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


d &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Decorators&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
d&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&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;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;99016179347006870000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class Decorators:
    # 本身就支持在类外部以类调用
    def log_function(func):
        def wrapper(*args, **kwargs):
            print(f&amp;quot;function start!&amp;quot;)
            print(f&amp;quot;args: {args}&amp;quot;)
            ret = func(*args, **kwargs)
            print(f&amp;quot;function end!&amp;quot;)
            return ret

        return wrapper

    @log_function
    def fib(self, n):
        if n &lt;= 1:
            return 0
        return self.fib(n - 1) + self.fib(n - 2)

    # 在 fib 后声明，防止影响上面的 fib 装饰器调用
    # 同时也提供给外面调用，即支持了在类外部以对象调用
    log_function = staticmethod(log_function)


d = Decorators()
d.fib(3)


@Decorators.log_function
def f():
    pass


@d.log_function
def g():
    pass


f()
g()`, `99016179347006870000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Decorators&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 本身就支持在类外部以类调用&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;log_function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function start!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;args: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            ret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; func&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;function end!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ret

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper

    @log_function
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# 在 fib 后声明，防止影响上面的 fib 装饰器调用&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 同时也提供给外面调用，即支持了在类外部以对象调用&lt;/span&gt;
    log_function &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;staticmethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;log_function&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


d &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Decorators&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
d&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fib&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


@Decorators&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_function
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;


@d&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log_function
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pass&lt;/span&gt;


f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;迭代器&quot;&gt;&lt;a href=&quot;#%E8%BF%AD%E4%BB%A3%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;迭代器&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;30996650481326117000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`lst = [1, 3, 5]
for i in lst:
    print(i)

d = {&amp;quot;a&amp;quot;: 1, &amp;quot;b&amp;quot;: 2}
for i in d:
    print(i)

with open(&amp;quot;my.txt&amp;quot;) as f:
    for i in f:
        print(i)`, `30996650481326117000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;lst &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; lst&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

d &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;my.txt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可迭代对象 + 迭代器，以上都可迭代&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;13459364431939824000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class NodeIter:
    def __init__(self, node):
        self.curr_node = node

    def __next__(self):
        if self.curr_node is None:
            raise StopIteration
        node, self.curr_node = self.curr_node, self.curr_node.next
        return node


class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):
        return NodeIter(self)


node1 = Node(&amp;quot;node1&amp;quot;)
node2 = Node(&amp;quot;node2&amp;quot;)
node3 = Node(&amp;quot;node3&amp;quot;)
node1.next = node2
node2.next = node3

for node in iter(node1):
    print(node.name)`, `13459364431939824000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NodeIter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__next__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; StopIteration
        node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; node


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NodeIter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


node1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node2
node2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node3

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; node &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上面的代码运行到 &lt;code class=&quot;language-text&quot;&gt;iter(node1)&lt;/code&gt; 就报错，说明迭代器不一定是可迭代对象（Iterator 不一定 Iterable），所以修改如下&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43041844853744780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class NodeIter:
    def __init__(self, node):
        self.curr_node = node

    def __next__(self): # 是迭代器，即 Iterator
        if self.curr_node is None:
            raise StopIteration
        node, self.curr_node = self.curr_node, self.curr_node.next
        return node

    def __iter__(self): # 是可迭代对象，即 Iterable
        return self


class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):
        return NodeIter(self)


node1 = Node(&amp;quot;node1&amp;quot;)
node2 = Node(&amp;quot;node2&amp;quot;)
node3 = Node(&amp;quot;node3&amp;quot;)
node1.next = node2
node2.next = node3

for node in iter(node1):
    print(node.name)`, `43041844853744780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py{11-12}&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py{11-12}&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight has-highlighted-lines&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NodeIter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__next__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 是迭代器，即 Iterator&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; StopIteration
        node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;curr_node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; node

&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 是可迭代对象，即 Iterable&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;gatsby-highlight-code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; NodeIter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


node1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node2
node2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node3

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; node &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;生成器&quot;&gt;&lt;a href=&quot;#%E7%94%9F%E6%88%90%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;生成器&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;39121957485250224000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def gen(num):
    while num &gt; 0:
        yield num
        num -= 1
    # 生成器函数里面 return 等价于 raise StopIteration
    # 即使 return 有值也不会被 next() 返回，只有 yield 的值才会返回
    # 如果一定要拿到 return 值，需要 catch StopIteration Exception
    return


g = gen(5)
first = next(g)

for i in g:
    print(i)

&gt;&gt;&gt;
4
3
2
1`, `39121957485250224000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; num
        num &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 生成器函数里面 return 等价于 raise StopIteration&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 即使 return 有值也不会被 next() 返回，只有 yield 的值才会返回&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# 如果一定要拿到 return 值，需要 catch StopIteration Exception&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;


g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
first &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;作用和迭代器一致，不同的点在于生成器函数 &lt;code class=&quot;language-text&quot;&gt;def gen(num):&lt;/code&gt; 和生成器对象 &lt;code class=&quot;language-text&quot;&gt;g = gen(5)&lt;/code&gt; 两种形式，而迭代器就是一个明确的 class&lt;/p&gt;
&lt;p&gt;对应的源码为&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5028892461303669000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`case TARGET(YIELD_VALUE): {
   retval = POP(); // 拿出栈顶值

   if (co-&gt;co_flags &amp; CO_ASYNC_GENERATOR) {
         PyObject *w = _PyAsyncGenValueWrapperNew(retval);
         Py_DECREF(retval);
         if (w == NULL) {
            retval = NULL;
            goto error;
         }
         retval = w;
   }

   f-&gt;f_stacktop = stack_pointer;
   goto exit_yielding;
}`, `5028892461303669000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;YIELD_VALUE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   retval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;POP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 拿出栈顶值&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;co&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;co_flags &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; CO_ASYNC_GENERATOR&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;w &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_PyAsyncGenValueWrapperNew&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;retval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;Py_DECREF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;retval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            retval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         retval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   f&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;f_stacktop &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stack_pointer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;goto&lt;/span&gt; exit_yielding&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以下生成器的写法比迭代器简洁&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54057062897299276000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):
        node = self
        while node is not None:
            yield node
            node = node.next


node1 = Node(&amp;quot;node1&amp;quot;)
node2 = Node(&amp;quot;node2&amp;quot;)
node3 = Node(&amp;quot;node3&amp;quot;)
node1.next = node2
node2.next = node3

for node in iter(node1):
    print(node.name)`, `54057062897299276000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; node &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; node
            node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt;


node1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
node1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node2
node2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node3

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; node &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;生成器还有 send 的用法&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78430708559677600000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def gen(num):
    while num &gt; 0:
        tmp = yield num
        if tmp is not None:
            num = tmp
        num -= 1


g = gen(5)
first = next(g) # 等价于 first = g.send(None)
print(f&amp;quot;first: {first}&amp;quot;)

print(f&amp;quot;send: {g.send(10)}&amp;quot;)

for i in g:
    print(i)`, `78430708559677600000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;num&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; num &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tmp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; num
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; tmp &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            num &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tmp
        num &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;


g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
first &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 等价于 first = g.send(None)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;first: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;send: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;闭包&quot;&gt;&lt;a href=&quot;#%E9%97%AD%E5%8C%85&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;闭包&lt;/h1&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;94761828726557840000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def f():
    data = []

    def inner(value):
        data.append(value)
        return data

    return inner


g = f()
print(g(1))
print(g(2))

&gt;&gt;&gt;
[1]
[1, 2]`, `94761828726557840000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner


g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以判断闭包的执行顺序&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;91725858820782200000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def f():
    data = []

    def inner(value):
        data.append(value)
        return data

    data = [0]

    return inner


g = f()
print(g(1))
print(g(2))

&gt;&gt;&gt;
[0, 1]
[0, 1, 2]`, `91725858820782200000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data

    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner


g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;cell 引用的闭包&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;63159985283675040000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`def f():
    data = []

    def inner(value):
        data.append(value)
        return data

    return inner


g = f() # 只要 g 存在就有对 data 的引用，即使 f 不存在了
print(g.__closure__) # 和下面的 g(3) 的地址完全一致，即是同一个 list
print(g(1))
print(g(2))
print(hex(id(g(3))))

&gt;&gt;&gt;
(&lt;cell at 0x7f7fbc6eafd0: list object at 0x7f7fbc7bcec0&gt;,)
[1]
[1, 2]
0x7f7fbc7bcec0`, `63159985283675040000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; inner


g &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; f&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 只要 g 存在就有对 data 的引用，即使 f 不存在了&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__closure__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 和下面的 g(3) 的地址完全一致，即是同一个 list&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;cell at &lt;span class=&quot;token number&quot;&gt;0x7f7fbc6eafd0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;object&lt;/span&gt; at &lt;span class=&quot;token number&quot;&gt;0x7f7fbc7bcec0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;0x7f7fbc7bcec0&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[命令行的艺术]]></title><description><![CDATA[基础 学习 Bash 的基础知识。具体地，在命令行中输入   并至少全文浏览一遍; 它理解起来很简单并且不冗长。其他的 shell 可能很好用，但 Bash 的功能已经足够强大到几乎总是可用的（ 如果你只学习 zsh，fish 或其他的 shell…]]></description><link>https://blog.towavephone.com/the-art-of-command-line/</link><guid isPermaLink="false">https://blog.towavephone.com/the-art-of-command-line/</guid><pubDate>Tue, 20 Feb 2024 14:38:08 GMT</pubDate><content:encoded>&lt;h1 id=&quot;基础&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E7%A1%80&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基础&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;学习 Bash 的基础知识。具体地，在命令行中输入 &lt;code class=&quot;language-text&quot;&gt;man bash&lt;/code&gt; 并至少全文浏览一遍; 它理解起来很简单并且不冗长。其他的 shell 可能很好用，但 Bash 的功能已经足够强大到几乎总是可用的（ 如果你只学习 zsh，fish 或其他的 shell 的话，在你自己的设备上会显得很方便，但过度依赖这些功能会给您带来不便，例如当你需要在服务器上工作时）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;熟悉至少一个基于文本的编辑器。通常而言 Vim （&lt;code class=&quot;language-text&quot;&gt;vi&lt;/code&gt;） 会是你最好的选择，毕竟在终端中编辑文本时 Vim 是最好用的工具（甚至大部分情况下 Vim 要比 Emacs、大型 IDE 或是炫酷的编辑器更好用）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会如何使用 &lt;code class=&quot;language-text&quot;&gt;man&lt;/code&gt; 命令去阅读文档。学会使用 &lt;code class=&quot;language-text&quot;&gt;apropos&lt;/code&gt; 去查找文档。知道有些命令并不对应可执行文件，而是在 Bash 内置好的，此时可以使用 &lt;code class=&quot;language-text&quot;&gt;help&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;help -d&lt;/code&gt; 命令获取帮助信息。你可以用 &lt;code class=&quot;language-text&quot;&gt;type 命令&lt;/code&gt; 来判断这个命令到底是可执行文件、shell 内置命令还是别名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用 &lt;code class=&quot;language-text&quot;&gt;&amp;gt;&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;&lt;/code&gt; 来重定向输出和输入，学会使用 &lt;code class=&quot;language-text&quot;&gt;|&lt;/code&gt; 来重定向管道。明白 &lt;code class=&quot;language-text&quot;&gt;&amp;gt;&lt;/code&gt; 会覆盖了输出文件而 &lt;code class=&quot;language-text&quot;&gt;&amp;gt;&amp;gt;&lt;/code&gt; 是在文件末添加。了解标准输出 stdout 和标准错误 stderr。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用通配符 &lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt; （或许再算上 &lt;code class=&quot;language-text&quot;&gt;?&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;[&lt;/code&gt;…&lt;code class=&quot;language-text&quot;&gt;]&lt;/code&gt;） 和引用以及引用中 &lt;code class=&quot;language-text&quot;&gt;&amp;#39;&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;&amp;quot;&lt;/code&gt; 的区别（后文中有一些具体的例子）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;熟悉 Bash 中的任务管理工具：&lt;code class=&quot;language-text&quot;&gt;&amp;amp;&lt;/code&gt;，&lt;strong&gt;ctrl-z&lt;/strong&gt;，&lt;strong&gt;ctrl-c&lt;/strong&gt;，&lt;code class=&quot;language-text&quot;&gt;jobs&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;fg&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;bg&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;kill&lt;/code&gt; 等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用 &lt;code class=&quot;language-text&quot;&gt;ssh&lt;/code&gt; 进行远程命令行登录，最好知道如何使用 &lt;code class=&quot;language-text&quot;&gt;ssh-agent&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;ssh-add&lt;/code&gt; 等命令来实现基础的无密码认证登录。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会基本的文件管理工具：&lt;code class=&quot;language-text&quot;&gt;ls&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ls -l&lt;/code&gt; （了解 &lt;code class=&quot;language-text&quot;&gt;ls -l&lt;/code&gt; 中每一列代表的意义），&lt;code class=&quot;language-text&quot;&gt;less&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;head&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;tail&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;tail -f&lt;/code&gt; （甚至 &lt;code class=&quot;language-text&quot;&gt;less +F&lt;/code&gt;），&lt;code class=&quot;language-text&quot;&gt;ln&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ln -s&lt;/code&gt; （了解硬链接与软链接的区别），&lt;code class=&quot;language-text&quot;&gt;chown&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;chmod&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;du&lt;/code&gt; （硬盘使用情况概述：&lt;code class=&quot;language-text&quot;&gt;du -hs *&lt;/code&gt;）。 关于文件系统的管理，学习 &lt;code class=&quot;language-text&quot;&gt;df&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;mount&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;fdisk&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;mkfs&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;lsblk&lt;/code&gt;。知道 inode 是什么（与 &lt;code class=&quot;language-text&quot;&gt;ls -i&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;df -i&lt;/code&gt; 等命令相关）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学习基本的网络管理工具：&lt;code class=&quot;language-text&quot;&gt;ip&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;ifconfig&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;dig&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学习并使用一种版本控制管理系统，例如 &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;熟悉正则表达式，学会使用 &lt;code class=&quot;language-text&quot;&gt;grep&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;egrep&lt;/code&gt;，它们的参数中 &lt;code class=&quot;language-text&quot;&gt;-i&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;-o&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;-v&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;-A&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;-B&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;-C&lt;/code&gt; 这些是很常用并值得认真学习的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用 &lt;code class=&quot;language-text&quot;&gt;apt-get&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;yum&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;dnf&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;pacman&lt;/code&gt; （具体使用哪个取决于你使用的 Linux 发行版）来查找和安装软件包。并确保你的环境中有 &lt;code class=&quot;language-text&quot;&gt;pip&lt;/code&gt; 来安装基于 Python 的命令行工具 （接下来提到的部分程序使用 &lt;code class=&quot;language-text&quot;&gt;pip&lt;/code&gt; 来安装会很方便）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;日常使用&quot;&gt;&lt;a href=&quot;#%E6%97%A5%E5%B8%B8%E4%BD%BF%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;日常使用&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 中，可以通过按 &lt;strong&gt;Tab&lt;/strong&gt; 键实现自动补全参数，使用 &lt;strong&gt;ctrl-r&lt;/strong&gt; 搜索命令行历史记录（按下按键之后，输入关键字便可以搜索，重复按下 &lt;strong&gt;ctrl-r&lt;/strong&gt; 会向后查找匹配项，按下 &lt;strong&gt;Enter&lt;/strong&gt; 键会执行当前匹配的命令，而按下右方向键会将匹配项放入当前行中，不会直接执行，以便做出修改）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 中，可以按下 &lt;strong&gt;ctrl-w&lt;/strong&gt; 删除你键入的最后一个单词，&lt;strong&gt;ctrl-u&lt;/strong&gt; 可以删除行内光标所在位置之前的内容，&lt;strong&gt;alt-b&lt;/strong&gt; 和 &lt;strong&gt;alt-f&lt;/strong&gt; 可以以单词为单位移动光标，&lt;strong&gt;ctrl-a&lt;/strong&gt; 可以将光标移至行首，&lt;strong&gt;ctrl-e&lt;/strong&gt; 可以将光标移至行尾，&lt;strong&gt;ctrl-k&lt;/strong&gt; 可以删除光标至行尾的所有内容，&lt;strong&gt;ctrl-l&lt;/strong&gt; 可以清屏。键入 &lt;code class=&quot;language-text&quot;&gt;man readline&lt;/code&gt; 可以查看 Bash 中的默认快捷键。内容有很多，例如 &lt;strong&gt;alt-.&lt;/strong&gt; 循环地移向前一个参数，而 &lt;strong&gt;alt-*&lt;/strong&gt; 可以展开通配符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你喜欢的话，可以执行 &lt;code class=&quot;language-text&quot;&gt;set -o vi&lt;/code&gt; 来使用 vi 风格的快捷键，而执行 &lt;code class=&quot;language-text&quot;&gt;set -o emacs&lt;/code&gt; 可以把它改回来。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了便于编辑长命令，在设置你的默认编辑器后（例如 &lt;code class=&quot;language-text&quot;&gt;export EDITOR=vim&lt;/code&gt;），&lt;strong&gt;ctrl-x&lt;/strong&gt; &lt;strong&gt;ctrl-e&lt;/strong&gt; 会打开一个编辑器来编辑当前输入的命令。在 vi 风格下快捷键则是 &lt;strong&gt;escape-v&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;键入 &lt;code class=&quot;language-text&quot;&gt;history&lt;/code&gt; 查看命令行历史记录，再用 &lt;code class=&quot;language-text&quot;&gt;!n&lt;/code&gt;（&lt;code class=&quot;language-text&quot;&gt;n&lt;/code&gt; 是命令编号）就可以再次执行。其中有许多缩写，最有用的大概就是 &lt;code class=&quot;language-text&quot;&gt;!$&lt;/code&gt;， 它用于指代上次键入的参数，而 &lt;code class=&quot;language-text&quot;&gt;!!&lt;/code&gt; 可以指代上次键入的命令了（参考 man 页面中的“HISTORY EXPANSION”）。不过这些功能，你也可以通过快捷键 &lt;strong&gt;ctrl-r&lt;/strong&gt; 和 &lt;strong&gt;alt-.&lt;/strong&gt; 来实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;cd&lt;/code&gt; 命令可以切换工作路径，输入 &lt;code class=&quot;language-text&quot;&gt;cd ~&lt;/code&gt; 可以进入 home 目录。要访问你的 home 目录中的文件，可以使用前缀 &lt;code class=&quot;language-text&quot;&gt;~&lt;/code&gt;（例如 &lt;code class=&quot;language-text&quot;&gt;~/.bashrc&lt;/code&gt;）。在 &lt;code class=&quot;language-text&quot;&gt;sh&lt;/code&gt; 脚本里则用环境变量 &lt;code class=&quot;language-text&quot;&gt;$HOME&lt;/code&gt; 指代 home 目录的路径。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;回到前一个工作路径：&lt;code class=&quot;language-text&quot;&gt;cd -&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你输入命令的时候中途改了主意，按下 &lt;strong&gt;alt-#&lt;/strong&gt; 在行首添加 &lt;code class=&quot;language-text&quot;&gt;#&lt;/code&gt; 把它当做注释再按下回车执行（或者依次按下 &lt;strong&gt;ctrl-a&lt;/strong&gt;， &lt;strong&gt;#&lt;/strong&gt;， &lt;strong&gt;enter&lt;/strong&gt;）。这样做的话，之后借助命令行历史记录，你可以很方便恢复你刚才输入到一半的命令。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;xargs&lt;/code&gt; （ 或 &lt;code class=&quot;language-text&quot;&gt;parallel&lt;/code&gt;）。他们非常给力。注意到你可以控制每行参数个数（&lt;code class=&quot;language-text&quot;&gt;-L&lt;/code&gt;）和最大并行数（&lt;code class=&quot;language-text&quot;&gt;-P&lt;/code&gt;）。如果你不确定它们是否会按你想的那样工作，先使用 &lt;code class=&quot;language-text&quot;&gt;xargs echo&lt;/code&gt; 查看一下。此外，使用 &lt;code class=&quot;language-text&quot;&gt;-I{}&lt;/code&gt; 会很方便。例如：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82888998158107150000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`find . -name &apos;*.py&apos; | xargs grep some_function
cat hosts | xargs -I{} ssh root@{} hostname`, `82888998158107150000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; -name &lt;span class=&quot;token string&quot;&gt;&apos;*.py&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;xargs&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; some_function
&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; hosts &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;xargs&lt;/span&gt; -I&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; root@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hostname&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pstree -p&lt;/code&gt; 以一种优雅的方式展示进程树。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;pgrep&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;pkill&lt;/code&gt; 根据名字查找进程或发送信号（&lt;code class=&quot;language-text&quot;&gt;-f&lt;/code&gt; 参数通常有用）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解你可以发往进程的信号的种类。比如，使用 &lt;code class=&quot;language-text&quot;&gt;kill -STOP [pid]&lt;/code&gt; 停止一个进程。使用 &lt;code class=&quot;language-text&quot;&gt;man 7 signal&lt;/code&gt; 查看详细列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;nohup&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;disown&lt;/code&gt; 使一个后台进程持续运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;netstat -lntp&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;ss -plat&lt;/code&gt; 检查哪些进程在监听端口（默认是检查 TCP 端口; 添加参数 &lt;code class=&quot;language-text&quot;&gt;-u&lt;/code&gt; 则检查 UDP 端口）或者 &lt;code class=&quot;language-text&quot;&gt;lsof -iTCP -sTCP:LISTEN -P -n&lt;/code&gt; (这也可以在 OS X 上运行)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lsof&lt;/code&gt; 来查看开启的套接字和文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;uptime&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;w&lt;/code&gt; 来查看系统已经运行多长时间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;alias&lt;/code&gt; 来创建常用命令的快捷形式。例如：&lt;code class=&quot;language-text&quot;&gt;alias ll=&amp;#39;ls -latr&amp;#39;&lt;/code&gt; 创建了一个新的命令别名 &lt;code class=&quot;language-text&quot;&gt;ll&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以把别名、shell 选项和常用函数保存在 &lt;code class=&quot;language-text&quot;&gt;~/.bashrc&lt;/code&gt;，具体看下这篇&lt;a href=&quot;http://superuser.com/a/183980/7106&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;文章&lt;/a&gt;。这样做的话你就可以在所有 shell 会话中使用你的设定。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把环境变量的设定以及登陆时要执行的命令保存在 &lt;code class=&quot;language-text&quot;&gt;~/.bash_profile&lt;/code&gt;。而对于从图形界面启动的 shell 和 &lt;code class=&quot;language-text&quot;&gt;cron&lt;/code&gt; 启动的 shell，则需要单独配置文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要想在几台电脑中同步你的配置文件（例如 &lt;code class=&quot;language-text&quot;&gt;.bashrc&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;.bash_profile&lt;/code&gt;），可以借助 Git。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当变量和文件名中包含空格的时候要格外小心。Bash 变量要用引号括起来，比如 &lt;code class=&quot;language-text&quot;&gt;&amp;quot;$FOO&amp;quot;&lt;/code&gt;。尽量使用 &lt;code class=&quot;language-text&quot;&gt;-0&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;-print0&lt;/code&gt; 选项以便用 NULL 来分隔文件名，例如 &lt;code class=&quot;language-text&quot;&gt;locate -0 pattern | xargs -0 ls -al&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;find / -print0 -type d | xargs -0 ls -al&lt;/code&gt;。如果 for 循环中循环访问的文件名含有空字符（空格、tab 等字符），只需用 &lt;code class=&quot;language-text&quot;&gt;IFS=$&amp;#39;\n&amp;#39;&lt;/code&gt; 把内部字段分隔符设为换行符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 脚本中，使用 &lt;code class=&quot;language-text&quot;&gt;set -x&lt;/code&gt; 去调试输出（或者使用它的变体 &lt;code class=&quot;language-text&quot;&gt;set -v&lt;/code&gt;，它会记录原始输入，包括多余的参数和注释）。尽可能地使用严格模式：使用 &lt;code class=&quot;language-text&quot;&gt;set -e&lt;/code&gt; 令脚本在发生错误时退出而不是继续运行；使用 &lt;code class=&quot;language-text&quot;&gt;set -u&lt;/code&gt; 来检查是否使用了未赋值的变量；试试 &lt;code class=&quot;language-text&quot;&gt;set -o pipefail&lt;/code&gt;，它可以监测管道中的错误。当牵扯到很多脚本时，使用 &lt;code class=&quot;language-text&quot;&gt;trap&lt;/code&gt; 来检测 ERR 和 EXIT。一个好的习惯是在脚本文件开头这样写，这会使它能够检测一些错误，并在错误发生时中断程序并输出信息：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;68593722616259870000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`set -euo pipefail
trap &amp;quot;echo &apos;error: Script failed: see failed command above&apos;&amp;quot; ERR`, `68593722616259870000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; -euo pipefail
&lt;span class=&quot;token builtin class-name&quot;&gt;trap&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;echo &apos;error: Script failed: see failed command above&apos;&quot;&lt;/span&gt; ERR&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 脚本中，子 shell（使用括号 &lt;code class=&quot;language-text&quot;&gt;(...)&lt;/code&gt;）是一种组织参数的便捷方式。一个常见的例子是临时地移动工作路径，代码如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18912053789931147000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# do something in current dir
(cd /some/other/dir &amp;&amp; other-command)
# continue in original dir`, `18912053789931147000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# do something in current dir&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cd /some/other/dir &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; other-command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# continue in original dir&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 中，变量有许多的扩展方式。&lt;code class=&quot;language-text&quot;&gt;${name:?error message}&lt;/code&gt; 用于检查变量是否存在。此外，当 Bash 脚本只需要一个参数时，可以使用这样的代码 &lt;code class=&quot;language-text&quot;&gt;input_file=${1:?usage: $0 input_file}&lt;/code&gt;。在变量为空时使用默认值：&lt;code class=&quot;language-text&quot;&gt;${name:-default}&lt;/code&gt;。如果你要在之前的例子中再加一个（可选的）参数，可以使用类似这样的代码 &lt;code class=&quot;language-text&quot;&gt;output_file=${2:-logfile}&lt;/code&gt;，如果省略了 $2，它的值就为空，于是 &lt;code class=&quot;language-text&quot;&gt;output_file&lt;/code&gt; 就会被设为 &lt;code class=&quot;language-text&quot;&gt;logfile&lt;/code&gt;。数学表达式：&lt;code class=&quot;language-text&quot;&gt;i=$(( (i + 1) % 5 ))&lt;/code&gt;。序列：&lt;code class=&quot;language-text&quot;&gt;{1..10}&lt;/code&gt;。截断字符串：&lt;code class=&quot;language-text&quot;&gt;${var%suffix}&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;${var#prefix}&lt;/code&gt;。例如，假设 &lt;code class=&quot;language-text&quot;&gt;var=foo.pdf&lt;/code&gt;，那么 &lt;code class=&quot;language-text&quot;&gt;echo ${var%.pdf}.txt&lt;/code&gt; 将输出 &lt;code class=&quot;language-text&quot;&gt;foo.txt&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用括号扩展（&lt;code class=&quot;language-text&quot;&gt;{&lt;/code&gt;…&lt;code class=&quot;language-text&quot;&gt;}&lt;/code&gt;）来减少输入相似文本，并自动化文本组合。这在某些情况下会很有用，例如 &lt;code class=&quot;language-text&quot;&gt;mv foo.{txt,pdf} some-dir&lt;/code&gt;（同时移动两个文件），&lt;code class=&quot;language-text&quot;&gt;cp somefile{,.bak}&lt;/code&gt;（会被扩展成 &lt;code class=&quot;language-text&quot;&gt;cp somefile somefile.bak&lt;/code&gt;）或者 &lt;code class=&quot;language-text&quot;&gt;mkdir -p test-{a,b,c}/subtest-{1,2,3}&lt;/code&gt;（会被扩展成所有可能的组合，并创建一个目录树）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过使用 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;(some command)&lt;/code&gt; 可以将输出视为文件。例如，对比本地文件 &lt;code class=&quot;language-text&quot;&gt;/etc/hosts&lt;/code&gt; 和一个远程文件：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;46641743681615890000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`diff /etc/hosts &lt;(ssh somehost cat /etc/hosts)`, `46641743681615890000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;diff /etc/hosts &amp;lt;(ssh somehost cat /etc/hosts)&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编写脚本时，你可能会想要把代码都放在大括号里。缺少右括号的话，代码就会因为语法错误而无法执行。如果你的脚本是要放在网上分享供他人使用的，这样的写法就体现出它的好处了，因为这样可以防止下载不完全代码被执行。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;55944142138582655000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
  # 在这里写代码
}`, `55944142138582655000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# 在这里写代码&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解 Bash 中的“here documents”，例如 &lt;code class=&quot;language-text&quot;&gt;cat &amp;lt;&amp;lt;EOF ...&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Bash 中，同时重定向标准输出和标准错误：&lt;code class=&quot;language-text&quot;&gt;some-command &amp;gt;logfile 2&amp;gt;&amp;amp;1&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;some-command &amp;amp;&amp;gt;logfile&lt;/code&gt;（如果只使用 &lt;code class=&quot;language-text&quot;&gt;&amp;gt;&lt;/code&gt;，则标准输出重定向到指定文件中，而标准错误输出到屏幕上）。通常，为了保证命令不会在标准输入里残留一个未关闭的文件句柄捆绑在你当前所在的终端上，在命令后添加 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;/dev/null&lt;/code&gt; 是一个好习惯。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;man ascii&lt;/code&gt; 查看具有十六进制和十进制值的 ASCII 表。&lt;code class=&quot;language-text&quot;&gt;man unicode&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;man utf-8&lt;/code&gt;，以及 &lt;code class=&quot;language-text&quot;&gt;man latin1&lt;/code&gt; 有助于你去了解通用的编码信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;screen&lt;/code&gt; 或 &lt;a href=&quot;https://tmux.github.io/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;tmux&lt;/code&gt;&lt;/a&gt; 来使用多份屏幕，当你在使用 ssh 时（保存 session 信息）将尤为有用。而 &lt;code class=&quot;language-text&quot;&gt;byobu&lt;/code&gt; 可以为它们提供更多的信息和易用的管理工具。另一个轻量级的 session 持久化解决方案是 &lt;a href=&quot;https://github.com/bogner/dtach&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dtach&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ssh 中，了解如何使用 &lt;code class=&quot;language-text&quot;&gt;-L&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;-D&lt;/code&gt;（偶尔需要用 &lt;code class=&quot;language-text&quot;&gt;-R&lt;/code&gt;）开启隧道是非常有用的，比如当你需要从一台远程服务器上访问 web 页面。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对 ssh 设置做一些小优化可能是很有用的，例如这个 &lt;code class=&quot;language-text&quot;&gt;~/.ssh/config&lt;/code&gt; 文件包含了防止特定网络环境下连接断开、压缩数据、多通道等选项：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50754135402657870000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
Compression=yes
ControlMaster auto
ControlPath /tmp/%r@%h:%p
ControlPersist yes`, `50754135402657870000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
Compression=yes
ControlMaster auto
ControlPath /tmp/%r@%h:%p
ControlPersist yes&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一些其他的关于 ssh 的选项是与安全相关的，应当小心翼翼的使用。例如你应当只能在可信任的网络中启用 &lt;code class=&quot;language-text&quot;&gt;StrictHostKeyChecking=no&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;ForwardAgent=yes&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑使用 &lt;a href=&quot;https://mosh.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;mosh&lt;/code&gt;&lt;/a&gt; 作为 ssh 的替代品，它使用 UDP 协议。它可以避免连接被中断并且对带宽需求更小，但它需要在服务端做相应的配置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取八进制形式的文件访问权限（修改系统设置时通常需要，但 &lt;code class=&quot;language-text&quot;&gt;ls&lt;/code&gt; 的功能不那么好用并且通常会搞砸），可以使用类似如下的代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45125180422726990000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`stat -c &apos;%A %a %n&apos; /etc/timezone`, `45125180422726990000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;stat -c &amp;#39;%A %a %n&amp;#39; /etc/timezone&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://github.com/mooz/percol&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;percol&lt;/code&gt;&lt;/a&gt; 或者 &lt;a href=&quot;https://github.com/junegunn/fzf&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;fzf&lt;/code&gt;&lt;/a&gt; 可以交互式地从另一个命令输出中选取值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;fpp&lt;/code&gt;（&lt;a href=&quot;https://github.com/facebook/PathPicker&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PathPicker&lt;/a&gt;）可以与基于另一个命令(例如 &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;）输出的文件交互。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 web 服务器上当前目录下所有的文件（以及子目录）暴露给你所处网络的所有用户，使用 &lt;code class=&quot;language-text&quot;&gt;python -m SimpleHTTPServer 7777&lt;/code&gt; （使用端口 7777 和 Python 2）或 &lt;code class=&quot;language-text&quot;&gt;python -m http.server 7777&lt;/code&gt; （使用端口 7777 和 Python 3）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以其他用户的身份执行命令，使用 &lt;code class=&quot;language-text&quot;&gt;sudo&lt;/code&gt;。默认以 root 用户的身份执行；使用 &lt;code class=&quot;language-text&quot;&gt;-u&lt;/code&gt; 来指定其他用户。使用 &lt;code class=&quot;language-text&quot;&gt;-i&lt;/code&gt; 来以该用户登录（需要输入你自己的密码）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 shell 切换为其他用户，使用 &lt;code class=&quot;language-text&quot;&gt;su username&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;su - username&lt;/code&gt;。加入 &lt;code class=&quot;language-text&quot;&gt;-&lt;/code&gt; 会使得切换后的环境与使用该用户登录后的环境相同。省略用户名则默认为 root。切换到哪个用户，就需要输入哪个用户的密码。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解命令行的 &lt;a href=&quot;https://wiki.debian.org/CommonErrorMessages/ArgumentListTooLong&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;128K 限制&lt;/a&gt;。使用通配符匹配大量文件名时，常会遇到 &lt;code class=&quot;language-text&quot;&gt;Argument list too long&lt;/code&gt; 的错误信息。（这种情况下换用 &lt;code class=&quot;language-text&quot;&gt;find&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;xargs&lt;/code&gt; 通常可以解决。）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当你需要一个基本的计算器时，可以使用 &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt; 解释器（当然你要用 python 的时候也是这样）。例如：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50697512008290490000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&gt;&gt;&gt; 2+3
5`, `50697512008290490000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;gt;&amp;gt;&amp;gt; 2+3
5&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;文件及数据处理&quot;&gt;&lt;a href=&quot;#%E6%96%87%E4%BB%B6%E5%8F%8A%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;文件及数据处理&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在当前目录下通过文件名查找一个文件，使用类似于这样的命令：&lt;code class=&quot;language-text&quot;&gt;find . -iname &amp;#39;*something*&amp;#39;&lt;/code&gt;。在所有路径下通过文件名查找文件，使用 &lt;code class=&quot;language-text&quot;&gt;locate something&lt;/code&gt; （但注意到 &lt;code class=&quot;language-text&quot;&gt;updatedb&lt;/code&gt; 可能没有对最近新建的文件建立索引，所以你可能无法定位到这些未被索引的文件）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://github.com/ggreer/the_silver_searcher&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ag&lt;/code&gt;&lt;/a&gt; 在源代码或数据文件里检索（&lt;code class=&quot;language-text&quot;&gt;grep -r&lt;/code&gt; 同样可以做到，但相比之下 &lt;code class=&quot;language-text&quot;&gt;ag&lt;/code&gt; 更加先进）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 HTML 转为文本：&lt;code class=&quot;language-text&quot;&gt;curl https://www.baidu.com | lynx -dump -stdin&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Markdown，HTML 以及所有文档格式之间的转换，试试 &lt;a href=&quot;http://pandoc.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pandoc&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当你要处理棘手的 XML 时候，&lt;code class=&quot;language-text&quot;&gt;xmlstarlet&lt;/code&gt; 算是上古时代流传下来的神器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;http://stedolan.github.io/jq/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;jq&lt;/code&gt;&lt;/a&gt; 处理 JSON。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://github.com/0k/shyaml&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;shyaml&lt;/code&gt;&lt;/a&gt; 处理 YAML。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要处理 Excel 或 CSV 文件的话，&lt;a href=&quot;https://github.com/onyxfish/csvkit&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;csvkit&lt;/a&gt; 提供了 &lt;code class=&quot;language-text&quot;&gt;in2csv&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;csvcut&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;csvjoin&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;csvgrep&lt;/code&gt; 等方便易用的工具。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当你要处理 Amazon S3 相关的工作的时候，&lt;a href=&quot;https://github.com/s3tools/s3cmd&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;s3cmd&lt;/code&gt;&lt;/a&gt; 是一个很方便的工具而 &lt;a href=&quot;https://github.com/bloomreach/s4cmd&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;s4cmd&lt;/code&gt;&lt;/a&gt; 的效率更高。Amazon 官方提供的 &lt;a href=&quot;https://github.com/aws/aws-cli&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;aws&lt;/code&gt;&lt;/a&gt; 以及 &lt;a href=&quot;https://github.com/donnemartin/saws&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;saws&lt;/code&gt;&lt;/a&gt; 是其他 AWS 相关工作的基础，值得学习。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何使用 &lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;uniq&lt;/code&gt;，包括 uniq 的 &lt;code class=&quot;language-text&quot;&gt;-u&lt;/code&gt; 参数和 &lt;code class=&quot;language-text&quot;&gt;-d&lt;/code&gt; 参数，具体内容在后文单行脚本节中。另外可以了解一下 &lt;code class=&quot;language-text&quot;&gt;comm&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何使用 &lt;code class=&quot;language-text&quot;&gt;cut&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;paste&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;join&lt;/code&gt; 来更改文件。很多人都会使用 &lt;code class=&quot;language-text&quot;&gt;cut&lt;/code&gt;，但遗忘了 &lt;code class=&quot;language-text&quot;&gt;join&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何运用 &lt;code class=&quot;language-text&quot;&gt;wc&lt;/code&gt; 去计算新行数（&lt;code class=&quot;language-text&quot;&gt;-l&lt;/code&gt;），字符数（&lt;code class=&quot;language-text&quot;&gt;-m&lt;/code&gt;），单词数（&lt;code class=&quot;language-text&quot;&gt;-w&lt;/code&gt;）以及字节数（&lt;code class=&quot;language-text&quot;&gt;-c&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何使用 &lt;code class=&quot;language-text&quot;&gt;tee&lt;/code&gt; 将标准输入复制到文件甚至标准输出，例如 &lt;code class=&quot;language-text&quot;&gt;ls -al | tee file.txt&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要进行一些复杂的计算，比如分组、逆序和一些其他的统计分析，可以考虑使用 &lt;a href=&quot;https://www.gnu.org/software/datamash/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;datamash&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意到语言设置（中文或英文等）对许多命令行工具有一些微妙的影响，比如排序的顺序和性能。大多数 Linux 的安装过程会将 &lt;code class=&quot;language-text&quot;&gt;LANG&lt;/code&gt; 或其他有关的变量设置为符合本地的设置。要意识到当你改变语言设置时，排序的结果可能会改变。明白国际化可能会使 sort 或其他命令运行效率下降许多倍。某些情况下（例如集合运算）你可以放心的使用 &lt;code class=&quot;language-text&quot;&gt;export LC_ALL=C&lt;/code&gt; 来忽略掉国际化并按照字节来判断顺序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你可以单独指定某一条命令的环境，只需在调用时把环境变量设定放在命令的前面，例如 &lt;code class=&quot;language-text&quot;&gt;TZ=Pacific/Fiji date&lt;/code&gt; 可以获取斐济的时间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何使用 &lt;code class=&quot;language-text&quot;&gt;awk&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;sed&lt;/code&gt; 来进行简单的数据处理。 参阅 &lt;a href=&quot;#one-liners&quot;&gt;One-liners&lt;/a&gt; 获取示例。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;替换一个或多个文件中出现的字符串：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;1142406573703302900&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`perl -pi.bak -e &apos;s/old-string/new-string/g&apos; my-files-*.txt`, `1142406573703302900`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;perl -pi.bak -e &amp;#39;s/old-string/new-string/g&amp;#39; my-files-*.txt&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://github.com/jlevy/repren&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;repren&lt;/code&gt;&lt;/a&gt; 来批量重命名文件，或是在多个文件中搜索替换内容。（有些时候 &lt;code class=&quot;language-text&quot;&gt;rename&lt;/code&gt; 命令也可以批量重命名，但要注意，它在不同 Linux 发行版中的功能并不完全一样。）&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;94315355183374930000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# 将文件、目录和内容全部重命名 foo -&gt; bar:
repren --full --preserve-case --from foo --to bar .
# 还原所有备份文件 whatever.bak -&gt; whatever:
repren --renames --from &apos;(.*)\.bak&apos; --to &apos;\1&apos; *.bak
# 用 rename 实现上述功能（若可用）:
rename &apos;s/\.bak\$//&apos; *.bak`, `94315355183374930000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;# 将文件、目录和内容全部重命名 foo -&amp;gt; bar:
repren --full --preserve-case --from foo --to bar .
# 还原所有备份文件 whatever.bak -&amp;gt; whatever:
repren --renames --from &amp;#39;(.*)\.bak&amp;#39; --to &amp;#39;\1&amp;#39; *.bak
# 用 rename 实现上述功能（若可用）:
rename &amp;#39;s/\.bak$//&amp;#39; *.bak&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据 man 页面的描述，&lt;code class=&quot;language-text&quot;&gt;rsync&lt;/code&gt; 是一个快速且非常灵活的文件复制工具。它闻名于设备之间的文件同步，但其实它在本地情况下也同样有用。在安全设置允许下，用 &lt;code class=&quot;language-text&quot;&gt;rsync&lt;/code&gt; 代替 &lt;code class=&quot;language-text&quot;&gt;scp&lt;/code&gt; 可以实现文件续传，而不用重新从头开始。它同时也是删除大量文件的&lt;a href=&quot;https://web.archive.org/web/20130929001850/http://linuxnote.net/jianingy/en/linux/a-fast-way-to-remove-huge-number-of-files.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;最快方法&lt;/a&gt;之一：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;49633961289570430000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`mkdir empty &amp;&amp; rsync -r --delete empty/ some-dir &amp;&amp; rmdir some-dir`, `49633961289570430000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir empty &amp;amp;&amp;amp; rsync -r --delete empty/ some-dir &amp;amp;&amp;amp; rmdir some-dir&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若要在复制文件时获取当前进度，可使用 &lt;code class=&quot;language-text&quot;&gt;pv&lt;/code&gt;，&lt;a href=&quot;https://github.com/dmerejkowsky/pycp&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pycp&lt;/code&gt;&lt;/a&gt;，&lt;a href=&quot;https://github.com/Xfennec/progress&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;progress&lt;/code&gt;&lt;/a&gt;，&lt;code class=&quot;language-text&quot;&gt;rsync --progress&lt;/code&gt;。若所执行的复制为 block 块拷贝，可以使用 &lt;code class=&quot;language-text&quot;&gt;dd status=progress&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;shuf&lt;/code&gt; 可以以行为单位来打乱文件的内容或从一个文件中随机选取多行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解 &lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; 的参数。显示数字时，使用 &lt;code class=&quot;language-text&quot;&gt;-n&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;-h&lt;/code&gt; 来显示更易读的数（例如 &lt;code class=&quot;language-text&quot;&gt;du -h&lt;/code&gt; 的输出）。明白排序时关键字的工作原理（&lt;code class=&quot;language-text&quot;&gt;-t&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;-k&lt;/code&gt;）。例如，注意到你需要 &lt;code class=&quot;language-text&quot;&gt;-k1，1&lt;/code&gt; 来仅按第一个域来排序，而 &lt;code class=&quot;language-text&quot;&gt;-k1&lt;/code&gt; 意味着按整行排序。稳定排序（&lt;code class=&quot;language-text&quot;&gt;sort -s&lt;/code&gt;）在某些情况下很有用。例如，以第二个域为主关键字，第一个域为次关键字进行排序，你可以使用 &lt;code class=&quot;language-text&quot;&gt;sort -k1，1 | sort -s -k2，2&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你想在 Bash 命令行中写 tab 制表符，按下 &lt;strong&gt;ctrl-v&lt;/strong&gt; &lt;strong&gt;[Tab]&lt;/strong&gt; 或键入 &lt;code class=&quot;language-text&quot;&gt;$&amp;#39;\t&amp;#39;&lt;/code&gt; （后者可能更好，因为你可以复制粘贴它）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;标准的源代码对比及合并工具是 &lt;code class=&quot;language-text&quot;&gt;diff&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;patch&lt;/code&gt;。使用 &lt;code class=&quot;language-text&quot;&gt;diffstat&lt;/code&gt; 查看变更总览数据。注意到 &lt;code class=&quot;language-text&quot;&gt;diff -r&lt;/code&gt; 对整个文件夹有效。使用 &lt;code class=&quot;language-text&quot;&gt;diff -r tree1 tree2 | diffstat&lt;/code&gt; 查看变更的统计数据。&lt;code class=&quot;language-text&quot;&gt;vimdiff&lt;/code&gt; 用于比对并编辑文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于二进制文件，使用 &lt;code class=&quot;language-text&quot;&gt;hd&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;hexdump&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;xxd&lt;/code&gt; 使其以十六进制显示，使用 &lt;code class=&quot;language-text&quot;&gt;bvi&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;hexedit&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;biew&lt;/code&gt; 来进行二进制编辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同样对于二进制文件，&lt;code class=&quot;language-text&quot;&gt;strings&lt;/code&gt;（包括 &lt;code class=&quot;language-text&quot;&gt;grep&lt;/code&gt; 等工具）可以帮助在二进制文件中查找特定比特。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;制作二进制差分文件（Delta 压缩），使用 &lt;code class=&quot;language-text&quot;&gt;xdelta3&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;iconv&lt;/code&gt; 更改文本编码。需要更高级的功能，可以使用 &lt;code class=&quot;language-text&quot;&gt;uconv&lt;/code&gt;，它支持一些高级的 Unicode 功能。例如，这条命令移除了所有重音符号：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88097615911085790000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`uconv -f utf-8 -t utf-8 -x &apos;::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] &gt;; ::Any-NFC; &apos; &lt; input.txt &gt; output.txt`, `88097615911085790000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;uconv -f utf-8 -t utf-8 -x &amp;#39;::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] &amp;gt;; ::Any-NFC; &amp;#39; &amp;lt; input.txt &amp;gt; output.txt&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拆分文件可以使用 &lt;code class=&quot;language-text&quot;&gt;split&lt;/code&gt;（按大小拆分）和 &lt;code class=&quot;language-text&quot;&gt;csplit&lt;/code&gt;（按模式拆分）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;操作日期和时间表达式，可以用 &lt;a href=&quot;http://www.fresse.org/dateutils/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dateutils&lt;/code&gt;&lt;/a&gt; 中的 &lt;code class=&quot;language-text&quot;&gt;dateadd&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;datediff&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;strptime&lt;/code&gt; 等工具。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;zless&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;zmore&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;zcat&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;zgrep&lt;/code&gt; 对压缩过的文件进行操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件属性可以通过 &lt;code class=&quot;language-text&quot;&gt;chattr&lt;/code&gt; 进行设置，它比文件权限更加底层。例如，为了保护文件不被意外删除，可以使用不可修改标记：&lt;code class=&quot;language-text&quot;&gt;sudo chattr +i /critical/directory/or/file&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;getfacl&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;setfacl&lt;/code&gt; 以保存和恢复文件权限。例如：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40935924014880880000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`getfacl -R /some/path &gt; permissions.txt
setfacl --restore=permissions.txt`, `40935924014880880000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;getfacl -R /some/path &amp;gt; permissions.txt
setfacl --restore=permissions.txt&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了高效地创建空文件，请使用 &lt;code class=&quot;language-text&quot;&gt;truncate&lt;/code&gt;（创建&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%A8%80%E7%96%8F%E6%96%87%E4%BB%B6&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;稀疏文件&lt;/a&gt;），&lt;code class=&quot;language-text&quot;&gt;fallocate&lt;/code&gt;（用于 ext4，xfs，btrf 和 ocfs2 文件系统），&lt;code class=&quot;language-text&quot;&gt;xfs_mkfile&lt;/code&gt;（适用于几乎所有的文件系统，包含在 xfsprogs 包中），&lt;code class=&quot;language-text&quot;&gt;mkfile&lt;/code&gt;（用于类 Unix 操作系统，比如 Solaris 和 Mac OS）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;系统调试&quot;&gt;&lt;a href=&quot;#%E7%B3%BB%E7%BB%9F%E8%B0%83%E8%AF%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;系统调试&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;curl&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;curl -I&lt;/code&gt; 可以被轻松地应用于 web 调试中，它们的好兄弟 &lt;code class=&quot;language-text&quot;&gt;wget&lt;/code&gt; 也是如此，或者也可以试试更潮的 &lt;a href=&quot;https://github.com/jkbrzt/httpie&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;httpie&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取 CPU 和硬盘的使用状态，通常使用使用 &lt;code class=&quot;language-text&quot;&gt;top&lt;/code&gt;（&lt;code class=&quot;language-text&quot;&gt;htop&lt;/code&gt; 更佳），&lt;code class=&quot;language-text&quot;&gt;iostat&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;iotop&lt;/code&gt;。而 &lt;code class=&quot;language-text&quot;&gt;iostat -mxz 15&lt;/code&gt; 可以让你获悉 CPU 和每个硬盘分区的基本信息和性能表现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;netstat&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ss&lt;/code&gt; 查看网络连接的细节。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dstat&lt;/code&gt; 在你想要对系统的现状有一个粗略的认识时是非常有用的。然而若要对系统有一个深度的总体认识，使用 &lt;a href=&quot;https://github.com/nicolargo/glances&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;glances&lt;/code&gt;&lt;/a&gt;，它会在一个终端窗口中向你提供一些系统级的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若要了解内存状态，运行并理解 &lt;code class=&quot;language-text&quot;&gt;free&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;vmstat&lt;/code&gt; 的输出。值得留意的是“cached”的值，它指的是 Linux 内核用来作为文件缓存的内存大小，而与空闲内存无关。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java 系统调试则是一件截然不同的事，一个可以用于 Oracle 的 JVM 或其他 JVM 上的调试的技巧是你可以运行 &lt;code class=&quot;language-text&quot;&gt;kill -3 &amp;lt;pid&amp;gt;&lt;/code&gt; 同时一个完整的栈轨迹和堆概述（包括 GC 的细节）会被保存到标准错误或是日志文件。JDK 中的 &lt;code class=&quot;language-text&quot;&gt;jps&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;jstat&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;jstack&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;jmap&lt;/code&gt; 很有用。&lt;a href=&quot;https://github.com/aragozin/jvm-tools&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;SJK tools&lt;/a&gt; 更高级。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;a href=&quot;http://www.bitwizard.nl/mtr/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;mtr&lt;/code&gt;&lt;/a&gt; 去跟踪路由，用于确定网络问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;a href=&quot;https://dev.yorhel.nl/ncdu&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ncdu&lt;/code&gt;&lt;/a&gt; 来查看磁盘使用情况，它比寻常的命令，如 &lt;code class=&quot;language-text&quot;&gt;du -sh *&lt;/code&gt;，更节省时间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查找正在使用带宽的套接字连接或进程，使用 &lt;a href=&quot;http://www.ex-parrot.com/~pdw/iftop/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;iftop&lt;/code&gt;&lt;/a&gt; 或 &lt;a href=&quot;https://github.com/raboof/nethogs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;nethogs&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt; 工具（Apache 中自带）可以简单粗暴地检查 web 服务器的性能。对于更复杂的负载测试，使用 &lt;code class=&quot;language-text&quot;&gt;siege&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wireshark.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;wireshark&lt;/code&gt;&lt;/a&gt;，&lt;a href=&quot;https://www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;tshark&lt;/code&gt;&lt;/a&gt; 和 &lt;a href=&quot;http://ngrep.sourceforge.net/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ngrep&lt;/code&gt;&lt;/a&gt; 可用于复杂的网络调试。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解 &lt;code class=&quot;language-text&quot;&gt;strace&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;ltrace&lt;/code&gt;。这俩工具在你的程序运行失败、挂起甚至崩溃，而你却不知道为什么或你想对性能有个总体的认识的时候是非常有用的。注意 profile 参数（&lt;code class=&quot;language-text&quot;&gt;-c&lt;/code&gt;）和附加到一个运行的进程参数 （&lt;code class=&quot;language-text&quot;&gt;-p&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解使用 &lt;code class=&quot;language-text&quot;&gt;ldd&lt;/code&gt; 来检查共享库。但是&lt;a href=&quot;http://www.catonmat.net/blog/ldd-arbitrary-code-execution/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;永远不要在不信任的文件上运行&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;了解如何运用 &lt;code class=&quot;language-text&quot;&gt;gdb&lt;/code&gt; 连接到一个运行着的进程并获取它的堆栈轨迹。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用 &lt;code class=&quot;language-text&quot;&gt;/proc&lt;/code&gt;。它在调试正在出现的问题的时候有时会效果惊人。比如：&lt;code class=&quot;language-text&quot;&gt;/proc/cpuinfo&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/meminfo&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/cmdline&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/xxx/cwd&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/xxx/exe&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/xxx/fd/&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;/proc/xxx/smaps&lt;/code&gt;（这里的 &lt;code class=&quot;language-text&quot;&gt;xxx&lt;/code&gt; 表示进程的 id 或 pid）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当调试一些之前出现的问题的时候，&lt;a href=&quot;http://sebastien.godard.pagesperso-orange.fr/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;sar&lt;/code&gt;&lt;/a&gt; 非常有用。它展示了 cpu、内存以及网络等的历史数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关于更深层次的系统分析以及性能分析，看看 &lt;code class=&quot;language-text&quot;&gt;stap&lt;/code&gt;（&lt;a href=&quot;https://sourceware.org/systemtap/wiki&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;SystemTap&lt;/a&gt;），&lt;a href=&quot;https://en.wikipedia.org/wiki/Perf_(Linux)&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;perf&lt;/code&gt;&lt;/a&gt;，以及 &lt;a href=&quot;https://github.com/draios/sysdig&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;sysdig&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看你当前使用的系统，使用 &lt;code class=&quot;language-text&quot;&gt;uname&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;uname -a&lt;/code&gt;（Unix／kernel 信息）或者 &lt;code class=&quot;language-text&quot;&gt;lsb_release -a&lt;/code&gt;（Linux 发行版信息）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无论什么东西工作得很欢乐（可能是硬件或驱动问题）时可以试试 &lt;code class=&quot;language-text&quot;&gt;dmesg&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你删除了一个文件，但通过 &lt;code class=&quot;language-text&quot;&gt;du&lt;/code&gt; 发现没有释放预期的磁盘空间，请检查文件是否被进程占用： &lt;code class=&quot;language-text&quot;&gt;lsof | grep deleted | grep &amp;quot;filename-of-my-big-file&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;单行脚本&quot;&gt;&lt;a href=&quot;#%E5%8D%95%E8%A1%8C%E8%84%9A%E6%9C%AC&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;单行脚本&lt;/h1&gt;
&lt;p&gt;一些命令组合的例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当你需要对文本文件做集合交、并、差运算时，&lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;uniq&lt;/code&gt; 会是你的好帮手。具体例子请参照代码后面的，此处假设 &lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt; 与 &lt;code class=&quot;language-text&quot;&gt;b&lt;/code&gt; 是两内容不同的文件。这种方式效率很高，并且在小文件和上 G 的文件上都能运用（注意尽管在 &lt;code class=&quot;language-text&quot;&gt;/tmp&lt;/code&gt; 在一个小的根分区上时你可能需要 &lt;code class=&quot;language-text&quot;&gt;-T&lt;/code&gt; 参数，但是实际上 &lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; 并不被内存大小约束），参阅前文中关于 &lt;code class=&quot;language-text&quot;&gt;LC_ALL&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; 的 &lt;code class=&quot;language-text&quot;&gt;-u&lt;/code&gt; 参数的部分。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;12524230269801430000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`sort a b | uniq &gt; c   # c 是 a 并 b
sort a b | uniq -d &gt; c   # c 是 a 交 b
sort a b b | uniq -u &gt; c   # c 是 a - b`, `12524230269801430000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;sort a b | uniq &amp;gt; c   # c 是 a 并 b
sort a b | uniq -d &amp;gt; c   # c 是 a 交 b
sort a b b | uniq -u &amp;gt; c   # c 是 a - b&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;grep . *&lt;/code&gt;（每行都会附上文件名）或者 &lt;code class=&quot;language-text&quot;&gt;head -100 *&lt;/code&gt;（每个文件有一个标题）来阅读检查目录下所有文件的内容。这在检查一个充满配置文件的目录（如 &lt;code class=&quot;language-text&quot;&gt;/sys&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;/proc&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;/etc&lt;/code&gt;）时特别好用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算文本文件第三列中所有数的和（可能比同等作用的 Python 代码快三倍且代码量少三倍）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;60522852746446045000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`awk &apos;{ x += \$3 } END { print x }&apos; myfile`, `60522852746446045000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;awk &amp;#39;{ x += $3 } END { print x }&amp;#39; myfile&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你想在文件树上查看大小/日期，这可能看起来像递归版的 &lt;code class=&quot;language-text&quot;&gt;ls -l&lt;/code&gt; 但比 &lt;code class=&quot;language-text&quot;&gt;ls -lR&lt;/code&gt; 更易于理解：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;11004487992957346000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`find . -type f -ls`, `11004487992957346000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;find . -type f -ls&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;假设你有一个类似于 web 服务器日志文件的文本文件，并且一个确定的值只会出现在某些行上，假设一个 &lt;code class=&quot;language-text&quot;&gt;acct_id&lt;/code&gt; 参数在 URI 中。如果你想计算出每个 &lt;code class=&quot;language-text&quot;&gt;acct_id&lt;/code&gt; 值有多少次请求，使用如下代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78039584038614780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`egrep -o &apos;acct_id=[0-9]+&apos; access.log | cut -d= -f2 | sort | uniq -c | sort -rn`, `78039584038614780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;egrep -o &amp;#39;acct_id=[0-9]+&amp;#39; access.log | cut -d= -f2 | sort | uniq -c | sort -rn&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要持续监测文件改动，可以使用 &lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt;，例如检查某个文件夹中文件的改变，可以用 &lt;code class=&quot;language-text&quot;&gt;watch -d -n 2 &amp;#39;ls -rtlh | tail&amp;#39;&lt;/code&gt;；或者在排查 WiFi 设置故障时要监测网络设置的更改，可以用 &lt;code class=&quot;language-text&quot;&gt;watch -d -n 2 ifconfig&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行这个函数从这篇文档中随机获取一条技巧（解析 Markdown 文件并抽取项目）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29984121331585724000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`function taocl() {
  curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README-zh.md|
     pandoc -f markdown -t html |
     iconv -f &apos;utf-8&apos; -t &apos;unicode&apos; |
     xmlstarlet fo --html --dropdtd |
     xmlstarlet sel -t -v &amp;quot;(html/body/ul/li[count(p)&gt;0])[\$RANDOM mod last()+1]&amp;quot; |
     xmlstarlet unesc | fmt -80
}`, `29984121331585724000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;function taocl() {
  curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README-zh.md|
     pandoc -f markdown -t html |
     iconv -f &amp;#39;utf-8&amp;#39; -t &amp;#39;unicode&amp;#39; |
     xmlstarlet fo --html --dropdtd |
     xmlstarlet sel -t -v &amp;quot;(html/body/ul/li[count(p)&amp;gt;0])[$RANDOM mod last()+1]&amp;quot; |
     xmlstarlet unesc | fmt -80
}&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;冷门但有用&quot;&gt;&lt;a href=&quot;#%E5%86%B7%E9%97%A8%E4%BD%86%E6%9C%89%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;冷门但有用&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;expr&lt;/code&gt;：计算表达式或正则匹配&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;m4&lt;/code&gt;：简单的宏处理器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;yes&lt;/code&gt;：多次打印字符串&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;cal&lt;/code&gt;：漂亮的日历&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt;：执行一个命令（脚本文件中很有用）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;printenv&lt;/code&gt;：打印环境变量（调试时或在写脚本文件时很有用）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;look&lt;/code&gt;：查找以特定字符串开头的单词或行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;cut&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;paste&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;join&lt;/code&gt;：数据修改&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;fmt&lt;/code&gt;：格式化文本段落&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pr&lt;/code&gt;：将文本格式化成页/列形式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;fold&lt;/code&gt;：包裹文本中的几行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;column&lt;/code&gt;：将文本格式化成多个对齐、定宽的列或表格&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;expand&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;unexpand&lt;/code&gt;：制表符与空格之间转换&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;nl&lt;/code&gt;：添加行号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;seq&lt;/code&gt;：打印数字&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;bc&lt;/code&gt;：计算器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;factor&lt;/code&gt;：分解因数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gnupg.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;gpg&lt;/code&gt;&lt;/a&gt;：加密并签名文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;toe&lt;/code&gt;：terminfo 入口列表&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;nc&lt;/code&gt;：网络调试及数据传输&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;socat&lt;/code&gt;：套接字代理，与 &lt;code class=&quot;language-text&quot;&gt;netcat&lt;/code&gt; 类似&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mattthias/slurm&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;slurm&lt;/code&gt;&lt;/a&gt;：网络流量可视化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dd&lt;/code&gt;：文件或设备间传输数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;file&lt;/code&gt;：确定文件类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;tree&lt;/code&gt;：以树的形式显示路径和文件，类似于递归的 &lt;code class=&quot;language-text&quot;&gt;ls&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;stat&lt;/code&gt;：文件信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;time&lt;/code&gt;：执行命令，并计算执行时间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;timeout&lt;/code&gt;：在指定时长范围内执行命令，并在规定时间结束后停止进程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lockfile&lt;/code&gt;：使文件只能通过 &lt;code class=&quot;language-text&quot;&gt;rm -f&lt;/code&gt; 移除&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;logrotate&lt;/code&gt;： 切换、压缩以及发送日志文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;watch&lt;/code&gt;：重复运行同一个命令，展示结果并／或高亮有更改的部分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/joh/when-changed&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;when-changed&lt;/code&gt;&lt;/a&gt;：当检测到文件更改时执行指定命令。参阅 &lt;code class=&quot;language-text&quot;&gt;inotifywait&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;entr&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;tac&lt;/code&gt;：反向输出文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;shuf&lt;/code&gt;：文件中随机选取几行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;comm&lt;/code&gt;：一行一行的比较排序过的文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;strings&lt;/code&gt;：从二进制文件中抽取文本&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;tr&lt;/code&gt;：转换字母&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;iconv&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;uconv&lt;/code&gt;：文本编码转换&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;split&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;csplit&lt;/code&gt;：分割文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;sponge&lt;/code&gt;：在写入前读取所有输入，在读取文件后再向同一文件写入时比较有用，例如 &lt;code class=&quot;language-text&quot;&gt;grep -v something some-file | sponge some-file&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;units&lt;/code&gt;：将一种计量单位转换为另一种等效的计量单位（参阅 &lt;code class=&quot;language-text&quot;&gt;/usr/share/units/definitions.units&lt;/code&gt;）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;apg&lt;/code&gt;：随机生成密码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;xz&lt;/code&gt;：高比例的文件压缩&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ldd&lt;/code&gt;：动态库信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;nm&lt;/code&gt;：提取 obj 文件中的符号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ab&lt;/code&gt; 或 &lt;a href=&quot;https://github.com/wg/wrk&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;wrk&lt;/code&gt;&lt;/a&gt;：web 服务器性能分析&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;strace&lt;/code&gt;：调试系统调用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.bitwizard.nl/mtr/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;mtr&lt;/code&gt;&lt;/a&gt;：更好的网络调试跟踪工具&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;cssh&lt;/code&gt;：可视化的并发 shell&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;rsync&lt;/code&gt;：通过 ssh 或本地文件系统同步文件和文件夹&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wireshark.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;wireshark&lt;/code&gt;&lt;/a&gt; 和 &lt;a href=&quot;https://www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;tshark&lt;/code&gt;&lt;/a&gt;：抓包和网络调试工具&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ngrep.sourceforge.net/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ngrep&lt;/code&gt;&lt;/a&gt;：网络层的 grep&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;host&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;dig&lt;/code&gt;：DNS 查找&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lsof&lt;/code&gt;：列出当前系统打开文件的工具以及查看端口信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dstat&lt;/code&gt;：系统状态查看&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nicolargo/glances&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;glances&lt;/code&gt;&lt;/a&gt;：高层次的多子系统总览&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;iostat&lt;/code&gt;：硬盘使用状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;mpstat&lt;/code&gt;： CPU 使用状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;vmstat&lt;/code&gt;： 内存使用状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;htop&lt;/code&gt;：top 的加强版&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;last&lt;/code&gt;：登入记录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;w&lt;/code&gt;：查看处于登录状态的用户&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;：用户/组 ID 信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://sebastien.godard.pagesperso-orange.fr/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;sar&lt;/code&gt;&lt;/a&gt;：系统历史数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ex-parrot.com/~pdw/iftop/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;iftop&lt;/code&gt;&lt;/a&gt; 或 &lt;a href=&quot;https://github.com/raboof/nethogs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;nethogs&lt;/code&gt;&lt;/a&gt;：套接字及进程的网络利用情况&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ss&lt;/code&gt;：套接字数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dmesg&lt;/code&gt;：引导及系统错误信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;sysctl&lt;/code&gt;： 在内核运行时动态地查看和修改内核的运行参数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;hdparm&lt;/code&gt;：SATA/ATA 磁盘更改及性能分析&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lsblk&lt;/code&gt;：列出块设备信息：以树形展示你的磁盘以及磁盘分区信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lshw&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;lscpu&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;lspci&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;lsusb&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;dmidecode&lt;/code&gt;：查看硬件信息，包括 CPU、BIOS、RAID、显卡、USB 设备等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;lsmod&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;modinfo&lt;/code&gt;：列出内核模块，并显示其细节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;fortune&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;ddate&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;sl&lt;/code&gt;：额，这主要取决于你是否认为蒸汽火车和莫名其妙的名人名言是否“有用”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;仅限-os-x-系统&quot;&gt;&lt;a href=&quot;#%E4%BB%85%E9%99%90-os-x-%E7%B3%BB%E7%BB%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;仅限 OS X 系统&lt;/h1&gt;
&lt;p&gt;以下是仅限于 OS X 系统的技巧。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;code class=&quot;language-text&quot;&gt;brew&lt;/code&gt; （Homebrew）或者 &lt;code class=&quot;language-text&quot;&gt;port&lt;/code&gt; （MacPorts）进行包管理。这些可以用来在 OS X 系统上安装以上的大多数命令。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;code class=&quot;language-text&quot;&gt;pbcopy&lt;/code&gt; 复制任何命令的输出到桌面应用，用 &lt;code class=&quot;language-text&quot;&gt;pbpaste&lt;/code&gt; 粘贴输入。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若要在 OS X 终端中将 Option 键视为 alt 键（例如在上面介绍的 &lt;strong&gt;alt-b&lt;/strong&gt;、&lt;strong&gt;alt-f&lt;/strong&gt; 等命令中用到），打开 偏好设置 -&gt; 描述文件 -&gt; 键盘 并勾选“使用 Option 键作为 Meta 键”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;code class=&quot;language-text&quot;&gt;open&lt;/code&gt; 或者 &lt;code class=&quot;language-text&quot;&gt;open -a /Applications/Whatever.app&lt;/code&gt; 使用桌面应用打开文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spotlight：用 &lt;code class=&quot;language-text&quot;&gt;mdfind&lt;/code&gt; 搜索文件，用 &lt;code class=&quot;language-text&quot;&gt;mdls&lt;/code&gt; 列出元数据（例如照片的 EXIF 信息）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意 OS X 系统是基于 BSD UNIX 的，许多命令（例如 &lt;code class=&quot;language-text&quot;&gt;ps&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;ls&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;tail&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;awk&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;sed&lt;/code&gt;）都和 Linux 中有微妙的不同（ Linux 很大程度上受到了 System V-style Unix 和 GNU 工具影响）。你可以通过标题为 “BSD General Commands Manual” 的 man 页面发现这些不同。在有些情况下 GNU 版本的命令也可能被安装（例如 &lt;code class=&quot;language-text&quot;&gt;gawk&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;gsed&lt;/code&gt; 对应 GNU 中的 awk 和 sed ）。如果要写跨平台的 Bash 脚本，避免使用这些命令（例如，考虑 Python 或者 &lt;code class=&quot;language-text&quot;&gt;perl&lt;/code&gt; ）或者经过仔细的测试。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;code class=&quot;language-text&quot;&gt;sw_vers&lt;/code&gt; 获取 OS X 的版本信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;仅限-windows-系统&quot;&gt;&lt;a href=&quot;#%E4%BB%85%E9%99%90-windows-%E7%B3%BB%E7%BB%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;仅限 Windows 系统&lt;/h1&gt;
&lt;p&gt;以下是仅限于 Windows 系统的技巧。&lt;/p&gt;
&lt;h2 id=&quot;在-windows-下获取-unix-工具&quot;&gt;&lt;a href=&quot;#%E5%9C%A8-windows-%E4%B8%8B%E8%8E%B7%E5%8F%96-unix-%E5%B7%A5%E5%85%B7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;在 Windows 下获取 Unix 工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可以安装 &lt;a href=&quot;https://cygwin.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Cygwin&lt;/a&gt; 允许你在 Microsoft Windows 中体验 Unix shell 的威力。这样的话，本文中介绍的大多数内容都将适用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Windows 10 上，你可以使用 &lt;a href=&quot;https://msdn.microsoft.com/commandline/wsl/about&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Bash on Ubuntu on Windows&lt;/a&gt;，它提供了一个熟悉的 Bash 环境，包含了不少 Unix 命令行工具。好处是它允许 Linux 上编写的程序在 Windows 上运行，而另一方面，Windows 上编写的程序却无法在 Bash 命令行中运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你在 Windows 上主要想用 GNU 开发者工具（例如 GCC），可以考虑 &lt;a href=&quot;http://www.mingw.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;MinGW&lt;/a&gt; 以及它的 &lt;a href=&quot;http://www.mingw.org/wiki/msys&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;MSYS&lt;/a&gt; 包，这个包提供了例如 bash，gawk，make 和 grep 的工具。MSYS 并不包含所有可以与 Cygwin 媲美的特性。当制作 Unix 工具的原生 Windows 端口时 MinGW 将特别地有用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;另一个在 Windows 下实现接近 Unix 环境外观效果的选项是 &lt;a href=&quot;https://github.com/dthree/cash&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Cash&lt;/a&gt;。注意在此环境下只有很少的 Unix 命令和命令行可用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;实用-windows-命令行工具&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%94%A8-windows-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实用 Windows 命令行工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可以使用 &lt;code class=&quot;language-text&quot;&gt;wmic&lt;/code&gt; 在命令行环境下给大部分 Windows 系统管理任务编写脚本以及执行这些任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows 实用的原生命令行网络工具包括 &lt;code class=&quot;language-text&quot;&gt;ping&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;ipconfig&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;tracert&lt;/code&gt;，和 &lt;code class=&quot;language-text&quot;&gt;netstat&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以使用 &lt;code class=&quot;language-text&quot;&gt;Rundll32&lt;/code&gt; 命令来实现&lt;a href=&quot;http://www.thewindowsclub.com/rundll32-shortcut-commands-windows&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;许多有用的 Windows 任务&lt;/a&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cygwin-技巧&quot;&gt;&lt;a href=&quot;#cygwin-%E6%8A%80%E5%B7%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cygwin 技巧&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;通过 Cygwin 的包管理器来安装额外的 Unix 程序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code class=&quot;language-text&quot;&gt;mintty&lt;/code&gt; 作为你的命令行窗口。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要访问 Windows 剪贴板，可以通过 &lt;code class=&quot;language-text&quot;&gt;/dev/clipboard&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行 &lt;code class=&quot;language-text&quot;&gt;cygstart&lt;/code&gt; 以通过默认程序打开一个文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要访问 Windows 注册表，可以使用 &lt;code class=&quot;language-text&quot;&gt;regtool&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意 Windows 驱动器路径 &lt;code class=&quot;language-text&quot;&gt;C:\&lt;/code&gt; 在 Cygwin 中用 &lt;code class=&quot;language-text&quot;&gt;/cygdrive/c&lt;/code&gt; 代表，而 Cygwin 的 &lt;code class=&quot;language-text&quot;&gt;/&lt;/code&gt; 代表 Windows 中的 &lt;code class=&quot;language-text&quot;&gt;C:\cygwin&lt;/code&gt;。要转换 Cygwin 和 Windows 风格的路径可以用 &lt;code class=&quot;language-text&quot;&gt;cygpath&lt;/code&gt;。这在需要调用 Windows 程序的脚本里很有用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会使用 &lt;code class=&quot;language-text&quot;&gt;wmic&lt;/code&gt;，你就可以从命令行执行大多数 Windows 系统管理任务，并编成脚本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要在 Windows 下获得 Unix 的界面和体验，另一个办法是使用 &lt;a href=&quot;https://github.com/dthree/cash&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Cash&lt;/a&gt;。需要注意的是，这个环境支持的 Unix 命令和命令行参数非常少。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要在 Windows 上获取 GNU 开发者工具（比如 GCC）的另一个办法是使用 &lt;a href=&quot;http://www.mingw.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;MinGW&lt;/a&gt; 以及它的 &lt;a href=&quot;http://www.mingw.org/wiki/msys&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;MSYS&lt;/a&gt; 软件包，该软件包提供了 bash、gawk、make、grep 等工具。然而 MSYS 提供的功能没有 Cygwin 完善。MinGW 在创建 Unix 工具的 Windows 原生移植方面非常有用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;免责声明&quot;&gt;&lt;a href=&quot;#%E5%85%8D%E8%B4%A3%E5%A3%B0%E6%98%8E&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;免责声明&lt;/h1&gt;
&lt;p&gt;除去特别小的工作，你编写的代码应当方便他人阅读。能力往往伴随着责任，你有能力在 Bash 中玩一些奇技淫巧并不意味着你应该去做！&lt;/p&gt;</content:encoded></item><item><title><![CDATA[深入理解 Python 虚拟机]]></title><description><![CDATA[提供的源代码链接如无特殊说明均来自于 CPython repo 中的 Python3.7 分支。 背景 这本书旨在深入 Python 解释器之中，提供 python 程序运行机制的概念上的概述。本书描述的对象是 CPython…]]></description><link>https://blog.towavephone.com/python-vm-deep-learn/</link><guid isPermaLink="false">https://blog.towavephone.com/python-vm-deep-learn/</guid><pubDate>Sun, 04 Feb 2024 10:39:38 GMT</pubDate><content:encoded>&lt;p&gt;提供的源代码链接如无特殊说明均来自于 CPython repo 中的 Python3.7 分支。&lt;/p&gt;
&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;这本书旨在深入 Python 解释器之中，提供 python 程序运行机制的概念上的概述。本书描述的对象是 CPython 的解释器实现，它是目前最主流或者说官方的 Python 实现。&lt;/p&gt;
&lt;p&gt;根据对解释器调用方式的不同，一个 Python 程序的执行可以分为两个或三个阶段，这些内容将会被涵盖在本书的不同章节中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始化（Initialization）：这个阶段涉及到对于 python 进程所需要的一系列数据结构的初始化。此阶段在交互模式下不会进行。&lt;/li&gt;
&lt;li&gt;编译（Compiling）：该阶段涉及到将源代码解析成语法树，创建 AST（Abstract Syntax Tree，抽象语法树）对象，创建 symbol tables 以及生成 code objects。&lt;/li&gt;
&lt;li&gt;解释运行（Interpreting）：这个阶段涉及到在一些环境（context）下对 code object 的执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从源代码生成解析树（parse tree）与 AST 的过程是一个与特定语言关系不大的过程，应用在其它语言上的方法一样可以应用在 Python 上，所以本书并不会将重点放在这里。另一方面从抽象语法树创建符号表与 code object 是编译过程中更为有趣的过程，它多多少少与 Python 更加相关，也更值得关注。对编译后的 code object 进行解释以及其中所涉及到的数据结构也将被提及。主题将包括但不限于：符号表、code object 的生成过程、Python objects、frame objects、code objects、function objects、Python opcodes、interpreter loop、生成器以及由用户定义的类。&lt;/p&gt;
&lt;h1 id=&quot;执行过程&quot;&gt;&lt;a href=&quot;#%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;执行过程&lt;/h1&gt;
&lt;p&gt;当我们通过命令行传入参数的方式调用 python 解释器去运行一个模块的时候，比如运行 &lt;code class=&quot;language-text&quot;&gt;python test.py&lt;/code&gt; 则如下过程将开始进行&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;stateDiagram
    a: Initialization
    state a {
      direction LR
      a1: main
      a2: Py_Main
      a1 --&gt; a2
    }
    b: Compilation
    a --&gt; b
    state b {
      direction LR
      b1: parse tree generation
      b2: AST generation
      b3: bytecode generation
      b4: bytecode optimization
      b5: code object generation
      b1 --&gt; b2
      b2 --&gt; b3
      b3 --&gt; b4
      b4 --&gt; b5
    }
    c: code object execution
    b --&gt; c&lt;/div&gt;
&lt;p&gt;Python 可执行程序是一个用 C 语言编写的程序。当它被执行的时候，所发生的事情其实就和其他 C 语言程序（比如 Linux 内核或是一个简单的 hello world 程序）差不多。请花一点时间来理解一下，Python 可执行程序只是一个用来运行程序的程序，可以结合 C 与 汇编或者 LLVM 的关系来理解。当我们使用解释器运行一个模块的时候，最开始会运行一个与平台相关的标准初始化过程。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本书中的所有讨论都假设在类 Unix 操作系统中，Windows 下会有些许不同。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C 运行时环境包含了所有的初始化操作，像是加载库、检查与设置环境变量等等，然后 python 可执行程序的 main 方法开始运行就像任何其他普通的 C 程序那样。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;补充知识：CPyhon 的项目结构&lt;/p&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://cpython-devguide.readthedocs.io/setup/#directory-structure&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;CPython 开发指南&lt;/a&gt; 与 &lt;a href=&quot;https://github.com/python/cpython/tree/3.7&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;3.7 的源码&lt;/a&gt;。CPython 项目中的大多数 C 代码位于少数几个文件夹中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Include：存放大多数解释器层面上主要的头文件。&lt;/li&gt;
&lt;li&gt;Objects：包括从 int 到 type 的一系列对象（object）的实现。&lt;/li&gt;
&lt;li&gt;Python：解释器、字节码编译器以及一些其它的基础设施。&lt;/li&gt;
&lt;li&gt;Parser：语法相关的代码，包括 parser，lexer 以及 parser generator。&lt;/li&gt;
&lt;li&gt;Modules：标准库中使用 C 实现的部分，以及 main.c。&lt;/li&gt;
&lt;li&gt;Programs：C 可执行文件的源代码，包括 CPython 解释器的程序入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;python 的 main 函数位于 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Programs/python.c#L13&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Programs/python.c&lt;/a&gt; 文件中。main 函数会调用位于 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Modules/main.c#L3085&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Modules/main.c&lt;/a&gt; 中的 &lt;code class=&quot;language-text&quot;&gt;Py_Main&lt;/code&gt; 函数，它处理解释器的初始化过程，包括：解析命令行参数、设置程序的 &lt;a href=&quot;https://docs.python.org/3.7/using/cmdline.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;flags&lt;/a&gt;、读取环境变量、运行 hook、哈希初始化等等。作为初始化过程的一部分，&lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pylifecycle.c#L1046&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Python/pylifecycle.c&lt;/a&gt; 中的 &lt;code class=&quot;language-text&quot;&gt;Py_Initialize&lt;/code&gt; 函数会被调用，它会初始化两个比较重要的数据结构：解释器状态与线程状态。&lt;/p&gt;
&lt;p&gt;看一眼解释器状态与线程状态的定义，它能给我们一些关于它们功能的信息。这两个数据结构由一些指向特定字段的指针组成，它们带有程序执行所需要的一些信息。首先我们来看解释器状态在源码中的定义（&lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/pystate.h#L113&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/pystate.h&lt;/a&gt;），以下是其中比较重要的部分：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72598453107129840000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`typedef struct _is {
    struct _is *next;
    struct _ts *tstate_head;

    PyObject *modules;
    PyObject *modules_by_index;
    PyObject *sysdict;
    PyObject *builtins;
    PyObject *importlib;

    PyObject *codec_search_path;
    PyObject *codec_search_cache;
    PyObject *codec_error_registry;

    int codecs_initialized;
    int fscodec_initialized;

    PyObject *builtins_copy;
} PyInterpreterState;`, `72598453107129840000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_is&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_is&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_ts&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;tstate_head&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;modules&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;modules_by_index&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;sysdict&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;builtins&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;importlib&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;codec_search_path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;codec_search_cache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;codec_error_registry&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; codecs_initialized&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; fscodec_initialized&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;builtins_copy&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; PyInterpreterState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Python 程序员应该或多或少会对这些字段名字中的词有一些认识比如：sysdict，builtins，codec 等。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;*next 字段为一个指向另一个解释器实例的引用（译注：多个解释器可以同时存在于一个进程当中，这个特性有望用于解决 CPython 的 GIL 问题，参考 &lt;a href=&quot;https://www.python.org/dev/peps/pep-0554/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PEP554&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;*tstate_head 字段指向运行的主线程。当程序开启多线程时，解释器被它开启的所有线程所共享。线程状态随后将被讨论。&lt;/li&gt;
&lt;li&gt;modules, &lt;code class=&quot;language-text&quot;&gt;modules_by_index&lt;/code&gt;, sysdict, builtins 以及 importlib 从它们的名字就能理解。它们都被定义为 PyObject 的实例，在 Python 虚拟机中 PyObject 为最基本的对象类型。&lt;/li&gt;
&lt;li&gt;codec* 相关的字段持有用于定位、加载编码方式（encodings）的信息，对于字节解码（decoding bytes）非常重要。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;程序的运行必须在一个线程之中进行，thread state structure 包含了一个线程执行 python code object 所需的信息，关于 thread state 定义的其中一部分代码如下（&lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/pystate.h#L212&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/pystate.h&lt;/a&gt;）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;63722804791767080000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`typedef struct _ts {

    struct _ts *prev;
    struct _ts *next;
    PyInterpreterState *interp;

    struct _frame *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                        to handle the runtime error. */
    char recursion_critical; /* The current calls must not cause
                                a stack overflow. */
    int stackcheck_counter;

    /* &apos;tracing&apos; keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    /* The exception currently being raised */
    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;
} PyThreadState;`, `63722804791767080000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_ts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_ts&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;prev&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_ts&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyInterpreterState &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;interp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_frame&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; recursion_depth&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; overflowed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* The stack has overflowed. Allow 50 more calls
                        to handle the runtime error. */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; recursion_critical&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* The current calls must not cause
                                a stack overflow. */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; stackcheck_counter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/* &apos;tracing&apos; keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; tracing&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; use_tracing&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    Py_tracefunc c_profilefunc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Py_tracefunc c_tracefunc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;c_profileobj&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;c_traceobj&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/* The exception currently being raised */&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;curexc_type&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;curexc_value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;curexc_traceback&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    PyObject &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;dict&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;/* Stores per-thread state */&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; gilstate_counter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; PyThreadState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关于这两个数据结构，后面的章节中还会有进一步讨论。初始化过程还会设置一些重要的机制，比如基本的 stdio 等。&lt;/p&gt;
&lt;p&gt;一旦完成了所有的初始化，&lt;code class=&quot;language-text&quot;&gt;Py_Main&lt;/code&gt; 函数会调用 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Modules/main.c#L426&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pymain_run_file&lt;/code&gt;&lt;/a&gt; 函数，随后发生调用：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62666455197511910000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`PyRun_AnyFileExFlags -&gt;
PyRun_SimpleFileExFlags -&gt;
PyRun_FileExFlags -&gt;
PyParser_ASTFromFileObject`, `62666455197511910000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PyRun_AnyFileExFlags -&amp;gt;
PyRun_SimpleFileExFlags -&amp;gt;
PyRun_FileExFlags -&amp;gt;
PyParser_ASTFromFileObject&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pythonrun.c#L373&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyRun_SimpleFileExFlags&lt;/a&gt; 函数调用中，&lt;code class=&quot;language-text&quot;&gt;__main__&lt;/code&gt; namespace 将被创建，文件内容将在其中被运行。它也将检查文件的 pyc 版本是否存在（pyc 文件是一个储存了已经被执行过的 py 文件编译后的代码（字节码）的文件）。当文件的 pyc 版本存在的时候，将会尝试以二进制形式读取并执行它。而当 pyc 文件不存在的时候，会顺着 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pythonrun.c#L965&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyRun_FileExFlags&lt;/a&gt; 向下调用，&lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pythonrun.c#L1210&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyParser_ASTFromFileObject&lt;/a&gt; 函数会接着调用 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Parser/parsetok.c#L116&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyParser_ParseFileObject&lt;/a&gt; 函数，它会读取被执行模块的内容并建立 parse tree，然后将 parse tree 传递给 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/ast.c#L768&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyAST_FromNodeObject&lt;/a&gt; 根据 parse tree 创建 AST。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你阅读这些代码，你会看到很多的 &lt;code class=&quot;language-text&quot;&gt;Py_INCREF&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;Py_DECREF&lt;/code&gt;。这些内存管理函数会在后面详细讨论。CPython 通过引用计数管理对象的生命周期。当一个新的对象引用产生的时候，计数通过 &lt;code class=&quot;language-text&quot;&gt;Py_INCREF&lt;/code&gt; 增加，当一个引用离开作用域的时候会通过 &lt;code class=&quot;language-text&quot;&gt;Py_DECREF&lt;/code&gt; 减少。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AST 生成后会被传给 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pythonrun.c#L1027&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;run_mod&lt;/a&gt; 函数，这个函数会接着调用 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/compile.c#L301&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyAST_CompileObject&lt;/a&gt; 根据 AST 生成 code object。并且在调用 &lt;code class=&quot;language-text&quot;&gt;PyAST_CompileObject&lt;/code&gt; 生成字节码的时候会经过一个简单的 peephole optimizer 进行字节码优化。随后 &lt;code class=&quot;language-text&quot;&gt;run_mod&lt;/code&gt; 会调用 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/ceval.c#L522&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyEval_EvalCode&lt;/a&gt; 随之产生函数调用:&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72659042598151370000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`PyEval_EvalCode -&gt;
PyEval_EvalCodeEx -&gt;
_PyEval_EvalCodeWithName -&gt;
PyEval_EvalFrameEx`, `72659042598151370000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PyEval_EvalCode -&amp;gt;
PyEval_EvalCodeEx -&amp;gt;
_PyEval_EvalCodeWithName -&amp;gt;
PyEval_EvalFrameEx&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/ceval.c#L544&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PyEval_EvalFrameEx&lt;/a&gt; 中的解释器循环（interpreter loop）才会实际进行对 code object 的运行处理。它不只是将 code object 作为参数而是使用一个 frame object，它有一个字段用来保存到 code object 的引用。简单来说，解释器循环会不断读取储存在一个指令数组中的下一个指令进行执行，增加或者删除位于栈上的对象，直到没有新的指令或者中途发生异常中断了循环。&lt;/p&gt;
&lt;p&gt;头文件 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/opcode.h&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/opcode.h&lt;/a&gt; 中包含了 python 虚拟机支持的 instruction/opcodes 清单。其实 opcodes 的概念很容易理解，在上面的例子中有 4 条 instruction：&lt;code class=&quot;language-text&quot;&gt;LOAD_FAST&lt;/code&gt; 将它的参数对应的值（这里对应 x）加载到栈上。python 虚拟机是基于栈的 （stack based），也就是说 opcode 进行求值的对象以及求值得到的结果都会保存在栈上。&lt;code class=&quot;language-text&quot;&gt;BINARY_MULTIPLY&lt;/code&gt; opcode 会将前两条指令的结果从栈中弹出（pop），执行二进制乘法运算然后将结果放（push）回栈顶。&lt;code class=&quot;language-text&quot;&gt;RETURN_VALUE&lt;/code&gt; 指令会从栈中弹出设置为返回值并中断解释器循环。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里还涉及到几个暂时没有被解答的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;LOAD_FAST 加载的值来自于哪里？&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 class=&quot;language-text&quot;&gt;Py_Main&lt;/code&gt; 将继续执行一些清理（clean up）过程。就像 &lt;code class=&quot;language-text&quot;&gt;Py_Initialize&lt;/code&gt; 所做的那样，&lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Python/pylifecycle.c#L1120&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Py_Finalize&lt;/a&gt; 会被调用完成一些清理任务，比如等待线程退出、调用 exit hooks、清理解释器分配的仍然被使用的内存等等。&lt;/p&gt;
&lt;p&gt;上面我们在一个较高的层面上对 python 解释器如何执行程序进行了描述，但还有大量的细节没有被讨论，接下来的章节中我们将深入进去提供更多的细节信息。&lt;/p&gt;
&lt;h1 id=&quot;编译-python-源代码&quot;&gt;&lt;a href=&quot;#%E7%BC%96%E8%AF%91-python-%E6%BA%90%E4%BB%A3%E7%A0%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;编译 Python 源代码&lt;/h1&gt;
&lt;p&gt;生成 code object 过程中 4 个主要的数据结构。尽管一般并不认为 Python 是一门编译性的语言，但实际上在代码执行前 Python 源码仍会被编译成可以被 python 虚拟机执行的字节码（byte code）。python 的编译过程非常直接，并不涉及到很多复杂的步骤。一个完整的 python 程序编译过程有以下几个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将源代码 parsing 成一个 parse tree。&lt;/li&gt;
&lt;li&gt;将 parse tree 转化为 AST（抽象语法树，abstract syntax tree）。&lt;/li&gt;
&lt;li&gt;生成符号表（symbol table）。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从 AST 生成 code object，这一步包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将 AST 转化为一个 flow control graph。&lt;/li&gt;
&lt;li&gt;从 control flow graph 产生 code object。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中将从源码到 AST 是一个标准的过程，python 这里并没有什么特别的，所以本书会把关注点更多放在 symbol tables 以及 code objects 的生成过程上。如果你对于 Parsing 的详细原理比较感兴趣，推荐阅读经典的 &lt;a href=&quot;https://www.amazon.co.uk/Compilers-Principles-Techniques-Alfred-Aho/dp/0201100886&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;龙书&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;从源码到-parse-tree&quot;&gt;&lt;a href=&quot;#%E4%BB%8E%E6%BA%90%E7%A0%81%E5%88%B0-parse-tree&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;从源码到 parse tree&lt;/h2&gt;
&lt;p&gt;按照 Dragon book 中的描述 python 的 parser 属于一种 &lt;a href=&quot;https://en.wikipedia.org/wiki/LL_parser&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;LL(1)&lt;/a&gt; parser。CPython 目录下的 &lt;a href=&quot;https://github.com/python/cpython/blob/3.5/Grammar/Grammar&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Grammar/Grammer&lt;/a&gt; 文件中包含了 Python 语言文法的 &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;EBNF&lt;/a&gt;（Extended Backus–Naur Form，扩展巴科斯范式）：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;50196387071062440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`...
stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (&apos;;&apos; small_stmt)* [&apos;;&apos;] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) |
                     (&apos;=&apos; (yield_expr|testlist_star_expr))*)
testlist_star_expr: (test|star_expr) (&apos;,&apos; (test|star_expr))* [&apos;,&apos;]
augassign: (&apos;+=&apos; | &apos;-=&apos; | &apos;*=&apos; | &apos;@=&apos; | &apos;/=&apos; | &apos;%=&apos; | &apos;&amp;=&apos; | &apos;|=&apos; | &apos;^=&apos; |
            &apos;&lt;&lt;=&apos; | &apos;&gt;&gt;=&apos; | &apos;**=&apos; | &apos;//=&apos;)
del_stmt: &apos;del&apos; exprlist
pass_stmt: &apos;pass&apos;
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
break_stmt: &apos;break&apos;
continue_stmt: &apos;continue&apos;
return_stmt: &apos;return&apos; [testlist]
yield_stmt: yield_expr
raise_stmt: &apos;raise&apos; [test [&apos;from&apos; test]]
import_stmt: import_name | import_from
import_name: &apos;import&apos; dotted_as_names
import_from: (&apos;from&apos; ((&apos;.&apos; | &apos;...&apos;)* dotted_name | (&apos;.&apos; | &apos;...&apos;)+)
              &apos;import&apos; (&apos;*&apos; | &apos;(&apos; import_as_names &apos;)&apos; | import_as_names))
import_as_name: NAME [&apos;as&apos; NAME]
...`, `50196387071062440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;...
stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (&amp;#39;;&amp;#39; small_stmt)* [&amp;#39;;&amp;#39;] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) |
                     (&amp;#39;=&amp;#39; (yield_expr|testlist_star_expr))*)
testlist_star_expr: (test|star_expr) (&amp;#39;,&amp;#39; (test|star_expr))* [&amp;#39;,&amp;#39;]
augassign: (&amp;#39;+=&amp;#39; | &amp;#39;-=&amp;#39; | &amp;#39;*=&amp;#39; | &amp;#39;@=&amp;#39; | &amp;#39;/=&amp;#39; | &amp;#39;%=&amp;#39; | &amp;#39;&amp;amp;=&amp;#39; | &amp;#39;|=&amp;#39; | &amp;#39;^=&amp;#39; |
            &amp;#39;&amp;lt;&amp;lt;=&amp;#39; | &amp;#39;&amp;gt;&amp;gt;=&amp;#39; | &amp;#39;**=&amp;#39; | &amp;#39;//=&amp;#39;)
del_stmt: &amp;#39;del&amp;#39; exprlist
pass_stmt: &amp;#39;pass&amp;#39;
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
break_stmt: &amp;#39;break&amp;#39;
continue_stmt: &amp;#39;continue&amp;#39;
return_stmt: &amp;#39;return&amp;#39; [testlist]
yield_stmt: yield_expr
raise_stmt: &amp;#39;raise&amp;#39; [test [&amp;#39;from&amp;#39; test]]
import_stmt: import_name | import_from
import_name: &amp;#39;import&amp;#39; dotted_as_names
import_from: (&amp;#39;from&amp;#39; ((&amp;#39;.&amp;#39; | &amp;#39;...&amp;#39;)* dotted_name | (&amp;#39;.&amp;#39; | &amp;#39;...&amp;#39;)+)
              &amp;#39;import&amp;#39; (&amp;#39;*&amp;#39; | &amp;#39;(&amp;#39; import_as_names &amp;#39;)&amp;#39; | import_as_names))
import_as_name: NAME [&amp;#39;as&amp;#39; NAME]
...&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;每次从命令行中运行一个 python 模块的时候，&lt;code class=&quot;language-text&quot;&gt;PyParser_ParseFileObject&lt;/code&gt; 函数会启动对这个模块的 parsing 过程，它会调用 tokenization 函数 &lt;code class=&quot;language-text&quot;&gt;PyTokenizer_FromFile&lt;/code&gt;，它接受模块的文件名作为参数，将模块文件的内容分解为一个个合法的 python tokens 或者发现语法错误时进行报错。&lt;/p&gt;
&lt;h2 id=&quot;python-tokens&quot;&gt;&lt;a href=&quot;#python-tokens&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Python tokens&lt;/h2&gt;
&lt;p&gt;Python 源码可以看作是一段由 token 组成的序列，比如说 return 就是一个 keyword token，字面量 -2 是一个数值字面量 token 等等。Parsing python 源码的第一步就是将源码分割为一个个的 token。Python 中存在以下几种 token：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;identifiers：由程序员定义的“名字”，包括函数名、变量名、类名等等。它们必须符合 python 文档中指定的&lt;a href=&quot;https://docs.python.org/3.5/reference/lexical_analysis.html#identifiers&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;规范&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;operators：一些特殊的符号比如 +, * 能够对值进行运算产生结果。&lt;/li&gt;
&lt;li&gt;delimiters：这类符号用来给表达式分组、提供标点、赋值操作，包括 (, ), {,}, =, *= 等。&lt;/li&gt;
&lt;li&gt;literals：字面量，这类符号用于提供一些类型的常量。比如字符串、bytes 类型的字面量：“Fred”, b”Fred”、数值类型的字面量 2、浮点类型字面量 1e100、虚数字面量 10j。&lt;/li&gt;
&lt;li&gt;comments：以 # 开头的字符串字面量，用户代码注释。comments 总是位于一个 physical line 的结尾。（译注：关于什么是 physical line 与 logical line 可以参考&lt;a href=&quot;https://stackoverflow.com/questions/31572589/difference-between-logical-line-and-physical-line-in-python&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;这里&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;NEWLINE：用于指示一行（logical line）的结束。&lt;/li&gt;
&lt;li&gt;INDENT 与 DEDENT：表示一组复合语句（compound statements）的缩进层级（identation levels）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一组 tokens 通过一个 NEWLINE token 被分割，组成一个 logical line，因而我们可以说一个 python 程序由一系列被 NEWLINE token 分割的 logical line 所组成。这些 logical line 可以映射到 python 语句（statements）。每一个 logical line 又由一系列 physical lines 所组成，physical line 的结尾是一个换行符（end-of-line）。多个 physical line 可以被连接在一起，连接可能是隐式的，比如在括号中的时候（包括 (), [], {} 三种），也可能是显式的，也就是在行尾使用一个反斜杠 \。（译注：原文中说的是 logical line 可以被连接，这里似乎存在错误，想表达的应该是 phycial lines 被连接为 logical line）。复合语句可以跨过多个 physical line，就像图 1.3.2 中所展示的那样。缩进对于 python 语句的分组非常重要，python grammar 中有一条规则用于对它进行描述：&lt;code class=&quot;language-text&quot;&gt;suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT&lt;/code&gt;，因而 python tokenizer 的一项主要任务就是生成 INDENT 和 DEDENT 到 parse tree。Tokenizer 使用一个栈来保持对缩进的跟踪，INDENT 与 DEDENT token 生成算法的伪代码如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32341831329671520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`使用 0 初始化 indent 栈
for 每一个 logical line:
    if (当前行的缩进级别 &gt; 栈顶的级别):
        push(当前缩进级别)
        生成一个 INDENT token
    if (当前行的缩进级别 &lt; 栈顶的级别):
        if (栈中不存在一个缩进级别 == 当前缩进级别):
            报告一个错误
        for 栈顶的每一个 != 当前行缩进级的值:
            移除这个值
            生成一个 DEDENT token
    tokenize(当前行)
对每一个栈上的值（除了0）生成 DEDENT token`, `32341831329671520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;使用 0 初始化 indent 栈
for 每一个 logical line:
    if (当前行的缩进级别 &amp;gt; 栈顶的级别):
        push(当前缩进级别)
        生成一个 INDENT token
    if (当前行的缩进级别 &amp;lt; 栈顶的级别):
        if (栈中不存在一个缩进级别 == 当前缩进级别):
            报告一个错误
        for 栈顶的每一个 != 当前行缩进级的值:
            移除这个值
            生成一个 DEDENT token
    tokenize(当前行)
对每一个栈上的值（除了0）生成 DEDENT token&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CPython 的实现中 &lt;code class=&quot;language-text&quot;&gt;PyTokenizer_FromFile&lt;/code&gt; 函数会按照从左到右，从上到下的对 python 源码文件进行扫描并进行 tokenize。空格字符除了终止符被用来分隔开 token，但这不是必须的。如果其中存在歧义，则按照从右到左合法的可能的最长字符串来理解。比如说 2 + 2，是一个字面量 2，一个 operator +，另一个字面量 2。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;补充知识：调用 python tokenize&lt;/p&gt;
&lt;p&gt;在 python 中提供了 tokenize 的接口（标准模块函数 &lt;a href=&quot;https://docs.python.org/3/library/tokenize.html#tokenize.tokenize&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;tokenize.tokenize&lt;/a&gt;）可以使用它对 python 源代码进行 tokenize，它接受一个 ByteIO.readline 的 callable 对象返回一个 tokens 的 generator，对这个 generator 进行迭代就能得到被解析模块的 tokens 序列，比如：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;39076719465606330000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&gt;&gt;&gt; from tokenize import tokenize
&gt;&gt;&gt; f = open(&amp;quot;./test.py&amp;quot;, &apos;rb&apos;)  # test.py 的内容只有一行 print(&amp;quot;hello&amp;quot;)
&gt;&gt;&gt; for t in tokenize(f.readline):
...     print(t)
...
TokenInfo(type=59 (ENCODING), string=&apos;utf-8&apos;, start=(0, 0), end=(0, 0), line=&apos;&apos;)
TokenInfo(type=1 (NAME), string=&apos;print&apos;, start=(1, 0), end=(1, 5), line=&apos;print(&amp;quot;hello&amp;quot;)&apos;)
TokenInfo(type=53 (OP), string=&apos;(&apos;, start=(1, 5), end=(1, 6), line=&apos;print(&amp;quot;hello&amp;quot;)&apos;)
TokenInfo(type=3 (STRING), string=&apos;&amp;quot;hello&amp;quot;&apos;, start=(1, 6), end=(1, 13), line=&apos;print(&amp;quot;hello&amp;quot;)&apos;)
TokenInfo(type=53 (OP), string=&apos;)&apos;, start=(1, 13), end=(1, 14), line=&apos;print(&amp;quot;hello&amp;quot;)&apos;)
TokenInfo(type=0 (ENDMARKER), string=&apos;&apos;, start=(2, 0), end=(2, 0), line=&apos;&apos;)`, `39076719465606330000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; from tokenize &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; tokenize
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; open&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./test.py&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;rb&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# test.py 的内容只有一行 print(&quot;hello&quot;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; tokenize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f.readline&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;:
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.     print&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;59&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ENCODING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;utf-8&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;print&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;print(&quot;hello&quot;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OP&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;(&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;print(&quot;hello&quot;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&quot;hello&quot;&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;print(&quot;hello&quot;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OP&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;)&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;print(&quot;hello&quot;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
TokenInfo&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ENDMARKER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;此外，还可以在命令行中调用 tokenize：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53640578165235290000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`\$ python -m tokenize test.py
0,0-0,0:            ENCODING       &apos;utf-8&apos;
1,0-1,5:            NAME           &apos;print&apos;
1,5-1,6:            OP             &apos;(&apos;
1,6-1,13:           STRING         &apos;&amp;quot;hello&amp;quot;&apos;
1,13-1,14:          OP             &apos;)&apos;
2,0-2,0:            ENDMARKER      &apos;&apos;`, `53640578165235290000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python -m tokenize test.py
&lt;span class=&quot;token number&quot;&gt;0,0&lt;/span&gt;-0,0:            ENCODING       &lt;span class=&quot;token string&quot;&gt;&apos;utf-8&apos;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1,0&lt;/span&gt;-1,5:            NAME           &lt;span class=&quot;token string&quot;&gt;&apos;print&apos;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1,5&lt;/span&gt;-1,6:            OP             &lt;span class=&quot;token string&quot;&gt;&apos;(&apos;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1,6&lt;/span&gt;-1,13:           STRING         &lt;span class=&quot;token string&quot;&gt;&apos;&quot;hello&quot;&apos;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;1,13&lt;/span&gt;-1,14:          OP             &lt;span class=&quot;token string&quot;&gt;&apos;)&apos;&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2,0&lt;/span&gt;-2,0:            ENDMARKER      &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;tokenizer 生成的 tokens 随后会被传递给 parser 根据 python 的语法来生成 parse tree 。当 parser 发现一个 token 违反了语法规则的时候就会抛出一个 SyntaxError。python 中有一个 &lt;a href=&quot;https://docs.python.org/3.5/library/parser.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;parser&lt;/a&gt; 模块提供了有限的对 parse tree 的访问能力：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;42249331304861200000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&gt;&gt;&gt; import parser
&gt;&gt;&gt; from pprint import pprint
&gt;&gt;&gt; code_str = &amp;quot;&amp;quot;&amp;quot;
... def hello_world():
...     return &apos;hello world&apos;
... &amp;quot;&amp;quot;&amp;quot;
&gt;&gt;&gt; st = parser.suite(code_str)
&gt;&gt;&gt; pprint(parser.st2list(st))
[257,
 [269,
  [295,
   [263,
    [1, &apos;def&apos;],
    [1, &apos;hello_world&apos;],
    [264, [7, &apos;(&apos;], [8, &apos;)&apos;]],
    [11, &apos;:&apos;],
    [304,
     [4, &apos;&apos;],
     [5, &apos;&apos;],
     [269,
      [270,
       [271,
        [278,
         [281,
          [1, &apos;return&apos;],
          [331,
           [305,
            [309,
             [310,
              [311,
               [312,
                [315,
                 [316,
                  [317,
                   [318,
                    [319,
                     [320,
                      [321,
                       [322,
                        [323, [324, [3, &amp;quot;&apos;hello world&apos;&amp;quot;]]]]]]]]]]]]]]]]]]]],
       [4, &apos;&apos;]]],
     [6, &apos;&apos;]]]]],
 [4, &apos;&apos;],s
 [0, &apos;&apos;]]`, `42249331304861200000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; parser
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; from pprint &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; pprint
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; code_str &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;
... def hello_world():
...     return &apos;hello world&apos;
... &quot;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; st &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parser.suite&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code_str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; pprint&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parser.st2list&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;st&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;257&lt;/span&gt;,
 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;269&lt;/span&gt;,
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;295&lt;/span&gt;,
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;263&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;def&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;hello_world&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;264&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;(&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;304&lt;/span&gt;,
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;269&lt;/span&gt;,
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;270&lt;/span&gt;,
       &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;271&lt;/span&gt;,
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;278&lt;/span&gt;,
         &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;281&lt;/span&gt;,
          &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;return&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
          &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;331&lt;/span&gt;,
           &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;305&lt;/span&gt;,
            &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;309&lt;/span&gt;,
             &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;310&lt;/span&gt;,
              &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;311&lt;/span&gt;,
               &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;312&lt;/span&gt;,
                &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;315&lt;/span&gt;,
                 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;316&lt;/span&gt;,
                  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;317&lt;/span&gt;,
                   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;318&lt;/span&gt;,
                    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;319&lt;/span&gt;,
                     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;320&lt;/span&gt;,
                      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;321&lt;/span&gt;,
                       &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;322&lt;/span&gt;,
                        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;323&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;324&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;&apos;hello world&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
       &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,s
 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;函数调用 parser.suite(source) 会返回一个 python 中对 parser tree 的中间表示即 parser tree (ST) 对象。之后的调用 parser.st2list 会将 parser 以 list 的形式返回。list 中第一个元素为一个 int 类型的数字表示 python grammar 中对应的 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/token.h&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;identifier 编号&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-25bb8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.138686131386855%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAAAoUlEQVQ4y62S6w6DIAyFQUE36cDp+7/ryvIt6Y9tSrDJCaEJ51Lq3LlaFA9FdJ1ViZJiVzwVmbMohlayu3k80/OKETRVVb8ZV8EQHlZ1MPFwZVaFqBt9gTwiFumnX24+6t7EzUTdEBQEF2YqiGeD8M+5/xJzMPdgZtz9+7MZRYE49BBOxE8tn3W0UgV0VcCZQCpXzdA6Xa+I7MyuJtbnTfoCRoYDs8SIwVwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2024 02 05 18 29 31&quot; title=&quot;&quot; data-src=&quot;/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-fee1c.png&quot; data-srcset=&quot;/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-a67b7.png 200w,
/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-0b187.png 400w,
/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-fee1c.png 800w,
/static/2024-02-05-18-29-31-cbc7be8e9be81adc14c75c8e7319d86d-25bb8.png 1096w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;上图展示了代码清单 1.3.2 中产生的 parse tree 结构，可以看到其中每个节点的类型可以用一个整数来表示，这些对于这些整数的定义位于头文件 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/token.h&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/token.h&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/graminit.h&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/graminit.h&lt;/a&gt; 中。&lt;/p&gt;
&lt;p&gt;在 CPython 虚拟机中，使用树结构来对 parser tree 进行表示。每一个产生式规则（production rule）是树中的一个节点（node）。对 node 的定义位于头文件 &lt;a href=&quot;https://github.com/python/cpython/blob/3.7/Include/node.h#L10&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Include/node.h&lt;/a&gt; 中。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74206888472923590000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`typedef struct _node {
    short       n_type;
    char        *n_str;
    int         n_lineno;
    int         n_col_offset;
    int         n_nchildren;
    struct _node    *n_child;
} node;`, `74206888472923590000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;c&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-c line-numbers&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_node&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;short&lt;/span&gt;       n_type&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;n_str&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;         n_lineno&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;         n_col_offset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;         n_nchildren&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;_node&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;n_child&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以对 parser tree 进行遍历，访问节点的类型、子节点、行号等等，这些操作以宏（macro）的形式定义在头文件 &lt;code class=&quot;language-text&quot;&gt;Include/node.h&lt;/code&gt; 中，用于与 parse tree 的节点进行交互。&lt;/p&gt;
&lt;p&gt;// TODO 深入理解 Python 虚拟机 &lt;a href=&quot;https://nanguage.gitbook.io/inside-python-vm-cn/untitled&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://nanguage.gitbook.io/inside-python-vm-cn/untitled&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[缓存报错重试机制探究]]></title><description><![CDATA[背景 在线上部署期间，或者用户长时间没有访问网页等等各种情况，有一定概率会出现以下形式的报错，导致网页白屏 原因与方案 由于现代前端工具链打包出来的，尤其是 webpack 的项目，其主入口默认不缓存，其他文件长期缓存，缓存的文件通过改变文件名（一般是 hash…]]></description><link>https://blog.towavephone.com/cache-error-retry-process/</link><guid isPermaLink="false">https://blog.towavephone.com/cache-error-retry-process/</guid><pubDate>Thu, 30 Nov 2023 17:47:38 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;在线上部署期间，或者用户长时间没有访问网页等等各种情况，有一定概率会出现以下形式的报错，导致网页白屏&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;45318483106547090000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Uncaught ChunkLoadError: Loading chunk &lt;CHUNK_NAME&gt; failed.
(error: &lt;WEBSITE_PATH&gt;/&lt;CHUNK_NAME&gt;-&lt;CHUNK_HASH&gt;.js)`, `45318483106547090000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Uncaught ChunkLoadError: Loading chunk &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CHUNK_NAME&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; failed.
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error: &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;WEBSITE_PATH&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;/&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CHUNK_NAME&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;-&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CHUNK_HASH&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;.js&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;原因与方案&quot;&gt;&lt;a href=&quot;#%E5%8E%9F%E5%9B%A0%E4%B8%8E%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;原因与方案&lt;/h1&gt;
&lt;p&gt;由于现代前端工具链打包出来的，尤其是 webpack 的项目，其主入口默认不缓存，其他文件长期缓存，缓存的文件通过改变文件名（一般是 hash）来更新&lt;/p&gt;
&lt;p&gt;所以在部署期间或者用户没有长时间打开网页，可能会请求已被删除的对应的 chunk 或者 chunk 虽然路径，文件名没变，但是内容改变导致有概率的白屏，所以针对此情况，有以下方案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;缓存所有版本的文件，但会造成空间浪费&lt;/li&gt;
&lt;li&gt;对打包出来的文件都不做缓存，但对服务器有很大压力&lt;/li&gt;
&lt;li&gt;可以通过一个 websocket 链接或者轮询来主动告知浏览器更新版本，但此方案过于繁重且需要后端支持&lt;/li&gt;
&lt;li&gt;对报错的文件重试，超过一定次数白屏，或者主动告知用户刷新页面&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综上，采用方案 4&lt;/p&gt;
&lt;h1 id=&quot;技术选型&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术选型&lt;/h1&gt;
&lt;p&gt;对于方案 4，同样有 2 大类，编译时或运行时，选型对比如下&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;序号&lt;/th&gt;
      &lt;th&gt;选型&lt;/th&gt;
      &lt;th&gt;优点&lt;/th&gt;
      &lt;th&gt;缺点&lt;/th&gt;
   &lt;/tr&gt;
   &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
      &lt;td rowspan=&quot;2&quot;&gt;运行时&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;
         &lt;a href=&quot;https://github.com/Nikaple/assets-retry&quot; target=&quot;_blank&quot;&gt;assets-retry&lt;/a&gt; 以及 &lt;a href=&quot;https://github.com/Nikaple/assets-retry/blob/master/README-cn.md&quot; target=&quot;_blank&quot;&gt;原理&lt;/a&gt;
      &lt;/td&gt;
      &lt;td&gt;接入简单&lt;/td&gt;
      &lt;td&gt;重试的元素种类太多，对于白屏问题，只需要保证 js 需要重试&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;
         重写 react lazy 方法，对报错进行重试
      &lt;/td&gt;
      &lt;td&gt;实现简单&lt;/td&gt;
      &lt;td&gt;
         &lt;ol&gt;
            &lt;li&gt;
               不能监听到主入口的报错，需要用 window.error 补齐
            &lt;/li&gt;
            &lt;li&gt;
               需要改动原始代码
            &lt;/li&gt;
            &lt;li&gt;
               由于 webpack 的 chunk 加载机制，失败的 chunk 不能重试，原理见上面
            &lt;/li&gt;
         &lt;/ol&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td rowspan=&quot;2&quot;&gt;编译时&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;
         &lt;a href=&quot;https://github.com/mattlewis92/webpack-retry-chunk-load-plugin&quot; target=&quot;_blank&quot;&gt;webpack-retry-chunk-load-plugin&lt;/a&gt;
      &lt;/td&gt;
      &lt;td&gt;
         接入简单，很方便的实现 chunk 重试
      &lt;/td&gt;
      &lt;td&gt;
         不能让主入口报错重试
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;
         &lt;a href=&quot;https://github.com/mattlewis92/webpack-retry-chunk-load-plugin&quot; target=&quot;_blank&quot;&gt;webpack-retry-chunk-load-plugin&lt;/a&gt; 以及
         &lt;a href=&quot;https://gist.github.com/mishani0x0ef/b15a969c016fa01b85f413b712c40fa1&quot; target=&quot;_blank&quot;&gt;html-tag-attributes-plugin&lt;/a&gt;
      &lt;/td&gt;
      &lt;td&gt;
         接入简单
      &lt;/td&gt;
      &lt;td&gt;
         需要自己实现主入口报错重试逻辑
      &lt;/td&gt;
    &lt;/tr&gt;
   &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;其他参考链接如下:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/igor_bykov/handle-loading-errors-fallback-with-htmlwebpackplugin-2d31&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;handle-loading-errors-fallback-with-htmlwebpackplugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://baijiahao.baidu.com/s?id=1776343703094638001&amp;#x26;wfr=spider&amp;#x26;for=pc&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;如何解决 JS 脚本加载失败的问题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69047420/webpack-code-splitting-chunkloaderror-loading-chunk-x-failed-but-the-chunk-e&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;webpack-code-splitting-chunkloaderror-loading-chunk-x-failed-but-the-chunk-e&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终采用选型 4&lt;/p&gt;
&lt;h1 id=&quot;实现过程&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现过程&lt;/h1&gt;
&lt;p&gt;预研过程包含选型 2 与 4，注意这里的重试需要有间隔时间以及需要带额外的 &lt;code class=&quot;language-text&quot;&gt;?&lt;/code&gt; 参数防止缓存生效&lt;/p&gt;
&lt;h2 id=&quot;重写-react-lazy-方法&quot;&gt;&lt;a href=&quot;#%E9%87%8D%E5%86%99-react-lazy-%E6%96%B9%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;重写 react lazy 方法&lt;/h2&gt;
&lt;p&gt;使用时可以直接替代 react 的 lazy 方法，但由于上面提到的缺点不采用此方案&lt;/p&gt;
&lt;p&gt;importRetry.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;7701117225646237000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import * as React from &apos;react&apos;;

const noop = () =&gt; {};
const strategies = {
   PARSE_ERROR_MESSAGE: (error, _) =&gt; {
      const url = new URL(error.request);
      return url.href;
   }
};

const defaultOpts = {
   strategy: &apos;PARSE_ERROR_MESSAGE&apos;,
   importFunction: (path) =&gt; import(/* @vite-ignore */ path),
   logger: noop
};

/**
 * Future improvements:
 * - cache successful variations to skip unnecessary lag on subsequent reloads
 */
// https://github.com/fatso83/retry-dynamic-import/blob/main/lib/retry.ts
function createDynamicImportWithRetry(maxRetries, opts = {}) {
   const resolvedOpts = {
      ...defaultOpts,
      ...opts
   };
   const { logger, importFunction, strategy } = resolvedOpts;

   return async (importer) =&gt; {
      try {
         return await importer();
      } catch (error) {
         logger(Date.now(), \`Importing failed: \`, error);

         const modulePath = strategies[strategy](error, importer);
         logger(\`Parsed url using \${strategy}:\${modulePath}\`);

         if (!modulePath) {
            logger(&apos;Unable to determine path to module when trying to reload&apos;);
            // nothing we can do ...
            throw error;
         }

         // retry x times with 2 second delay base and backoff factor of 2 (1/2, 1, 2, 4, 8 seconds)
         //
         for (let i = -1; i &lt; maxRetries; i++) {
            // add a timestamp to the url to force a reload the module (and not use the cached version - cache busting)
            let cacheBustedPath = \`\${modulePath}?t=\${+new Date()}\`;
            logger(Date.now(), \`Trying re-import module using cache busted path: \${cacheBustedPath}\`);

            try {
               return await importFunction(cacheBustedPath);
            } catch (e) {
               logger(\`Import for \${cacheBustedPath} failed\`);
               await new Promise((resolve) =&gt; setTimeout(resolve, 1000 * 2 ** i));
            }
         }
         throw error;
      }
   };
}

const MAX_RETRY_COUNT = 5;

const defaultDynamicImportWithRetry = createDynamicImportWithRetry(MAX_RETRY_COUNT, {
   logger: console.log
});

export function lazy(importer) {
   return process.env.NODE_ENV === &apos;development&apos;
      ? React.lazy
      : React.lazy(() =&gt; defaultDynamicImportWithRetry(importer));
}`, `7701117225646237000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;noop&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; strategies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function-variable function&quot;&gt;PARSE_ERROR_MESSAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultOpts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   strategy&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;PARSE_ERROR_MESSAGE&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token function-variable function&quot;&gt;importFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* @vite-ignore */&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   logger&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; noop
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * Future improvements:
 * - cache successful variations to skip unnecessary lag on subsequent reloads
 */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// https://github.com/fatso83/retry-dynamic-import/blob/main/lib/retry.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDynamicImportWithRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;maxRetries&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resolvedOpts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;defaultOpts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;opts
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; logger&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; importFunction&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; strategy &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resolvedOpts&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;importer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;importer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Importing failed: &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; modulePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; strategies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;strategy&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; importer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Parsed url using &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;strategy&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;modulePath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

         &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;modulePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Unable to determine path to module when trying to reload&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// nothing we can do ...&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

         &lt;span class=&quot;token comment&quot;&gt;// retry x times with 2 second delay base and backoff factor of 2 (1/2, 1, 2, 4, 8 seconds)&lt;/span&gt;
         &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; maxRetries&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// add a timestamp to the url to force a reload the module (and not use the cached version - cache busting)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; cacheBustedPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;modulePath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?t=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Trying re-import module using cache busted path: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cacheBustedPath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;importFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheBustedPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;token function&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Import for &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cacheBustedPath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; failed&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
               &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MAX_RETRY_COUNT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultDynamicImportWithRetry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDynamicImportWithRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MAX_RETRY_COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   logger&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;importer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;development&apos;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lazy
      &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defaultDynamicImportWithRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;importer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;webpack-retry-chunk-load-plugin--html-tag-attributes-plugin&quot;&gt;&lt;a href=&quot;#webpack-retry-chunk-load-plugin--html-tag-attributes-plugin&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;webpack-retry-chunk-load-plugin + html-tag-attributes-plugin&lt;/h2&gt;
&lt;p&gt;plugins/html-tag-attributes-plugin.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;65027463291803440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const HtmlWebpackPlugin = require(&apos;html-webpack-plugin&apos;);

// For more information about plugin concepts, see: https://github.com/jantimon/html-webpack-plugin#events
class HtmlTagAttributesPlugin {
   static name = &apos;HtmlTagAttributesPlugin&apos;;

   constructor(options) {
      const defaultOptions = {
         script: {}
      };

      this.options = { ...defaultOptions, ...options };
   }

   apply(compiler) {
      compiler.hooks.compilation.tap(HtmlTagAttributesPlugin.name, (compilation) =&gt;
         this._hookIntoHtmlAlterAssetTags(compilation)
      );
   }

   _hookIntoHtmlAlterAssetTags(compilation) {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(HtmlTagAttributesPlugin.name, (data, cb) =&gt;
         cb(null, this._extendScriptTags(data))
      );
   }

   _extendScriptTags(data) {
      data.assetTags.scripts = data.assetTags.scripts.map(({ attributes, ...other }) =&gt; ({
         ...other,
         attributes: {
            ...attributes,
            ...this.options.script
         }
      }));

      return data;
   }
}

module.exports = { HtmlTagAttributesPlugin };`, `65027463291803440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; HtmlWebpackPlugin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;html-webpack-plugin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// For more information about plugin concepts, see: https://github.com/jantimon/html-webpack-plugin#events&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HtmlTagAttributesPlugin&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;HtmlTagAttributesPlugin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         script&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;defaultOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      compiler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hooks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;compilation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HtmlTagAttributesPlugin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_hookIntoHtmlAlterAssetTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compilation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;_hookIntoHtmlAlterAssetTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      HtmlWebpackPlugin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHooks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compilation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alterAssetTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tapAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HtmlTagAttributesPlugin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
         &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_extendScriptTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token function&quot;&gt;_extendScriptTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assetTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scripts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assetTags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scripts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;other &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;other&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         attributes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;attributes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;script
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; HtmlTagAttributesPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;config-overrides.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2881807425521554400&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const { RetryChunkLoadPlugin } = require(&apos;webpack-retry-chunk-load-plugin&apos;);
const { HtmlTagAttributesPlugin } = require(&apos;./plugins/html-tag-attributes-plugin&apos;);

const lastResortScript = &amp;quot;window.alert(&apos;Frontend Is Deploying, Please Wait!&apos;); window.location.reload(true);&amp;quot;;
const retryDelay = 4000;
const maxRetries = 5;

const isProd = env === &apos;production&apos;;

const localConfig = override(
   isProd &amp;&amp;
      addWebpackPlugin(
         new RetryChunkLoadPlugin({
            // optional stringified function to get the cache busting query string appended to the script src
            // if not set will default to appending the string \`?cache-bust=true\`
            cacheBust: \`function() {
              return Date.now();
            }\`,
            // optional value to set the amount of time in milliseconds before trying to load the chunk again. Default is 0
            // if string, value must be code to generate a delay value. Receives retryCount as argument
            // e.g. \`function(retryAttempt) { return retryAttempt * 1000 }\`
            retryDelay,
            // optional value to set the maximum number of retries to load the chunk. Default is 1
            maxRetries,
            // // optional list of chunks to which retry script should be injected
            // // if not set will add retry script to all chunks that have webpack script loading
            // chunks: [&apos;chunkName&apos;],
            // optional code to be executed in the browser context if after all retries chunk is not loaded.
            // if not set - nothing will happen and error will be returned to the chunk loader.
            lastResortScript
         })
      ),
   isProd &amp;&amp;
      addWebpackPlugin(
         new HtmlTagAttributesPlugin({
            script: {
               onerror: \`
                // const isAsyncScript = event.target.defer || event.target.async
                // 暂时不包含对同步脚本的处理
                let retryCount = 0
                const retryFunc = async () =&gt; {
                  retryCount++
                  let cacheBustedPath = event.target.src + &apos;?t=&apos; + (+new Date())
                    const script = document.createElement(&apos;script&apos;)
                    script.src = cacheBustedPath
                    script.onerror = async () =&gt; {
                      await new Promise(resolve =&gt; setTimeout(resolve, \${retryDelay}))
                      if (retryCount === \${maxRetries}) {
                        \${lastResortScript}
                      } else {
                        retryFunc()
                      }
                    }
                    // webpack nonce for csp
                    const originalNonce = event.target.getAttribute(&apos;nonce&apos;)
                    if (originalNonce) {
                        script.setAttribute(&apos;nonce&apos;, originalNonce)
                    }
                    document.getElementsByTagName(&apos;head&apos;)[0].appendChild(script)
                }

                retryFunc()
              \`
            }
         })
      )
);`, `2881807425521554400`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; RetryChunkLoadPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;webpack-retry-chunk-load-plugin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; HtmlTagAttributesPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;./plugins/html-tag-attributes-plugin&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastResortScript &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;window.alert(&apos;Frontend Is Deploying, Please Wait!&apos;); window.location.reload(true);&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; retryDelay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; maxRetries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isProd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; env &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;production&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; localConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;override&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   isProd &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;addWebpackPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RetryChunkLoadPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// optional stringified function to get the cache busting query string appended to the script src&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// if not set will default to appending the string `?cache-bust=true`&lt;/span&gt;
            cacheBust&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;function() {
              return Date.now();
            }&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// optional value to set the amount of time in milliseconds before trying to load the chunk again. Default is 0&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// if string, value must be code to generate a delay value. Receives retryCount as argument&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// e.g. `function(retryAttempt) { return retryAttempt * 1000 }`&lt;/span&gt;
            retryDelay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// optional value to set the maximum number of retries to load the chunk. Default is 1&lt;/span&gt;
            maxRetries&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// // optional list of chunks to which retry script should be injected&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// // if not set will add retry script to all chunks that have webpack script loading&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// chunks: [&apos;chunkName&apos;],&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// optional code to be executed in the browser context if after all retries chunk is not loaded.&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// if not set - nothing will happen and error will be returned to the chunk loader.&lt;/span&gt;
            lastResortScript
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   isProd &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;addWebpackPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HtmlTagAttributesPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            script&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
               onerror&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
                // const isAsyncScript = event.target.defer || event.target.async
                // 暂时不包含对同步脚本的处理
                let retryCount = 0
                const retryFunc = async () =&gt; {
                  retryCount++
                  let cacheBustedPath = event.target.src + &apos;?t=&apos; + (+new Date())
                    const script = document.createElement(&apos;script&apos;)
                    script.src = cacheBustedPath
                    script.onerror = async () =&gt; {
                      await new Promise(resolve =&gt; setTimeout(resolve, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;retryDelay&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;))
                      if (retryCount === &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;maxRetries&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;) {
                        &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;lastResortScript&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
                      } else {
                        retryFunc()
                      }
                    }
                    // webpack nonce for csp
                    const originalNonce = event.target.getAttribute(&apos;nonce&apos;)
                    if (originalNonce) {
                        script.setAttribute(&apos;nonce&apos;, originalNonce)
                    }
                    document.getElementsByTagName(&apos;head&apos;)[0].appendChild(script)
                }

                retryFunc()
              &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;效果展示&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果展示&lt;/h1&gt;
&lt;p&gt;在主入口的 js 以及分包后的 chunk 报错后都会做重试 5 次的操作，超过 5 次弹窗提示用户在部署，点击确定后刷新页面再次重试&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-a01e8.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 64.06810035842294%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAABUklEQVQ4y6WS3U6EMBCFef/X8Fm89MobY9wNYpbfAi30n4XjdDZrNGrM4iQn0yHk6zltM601xlFCSsldTRNSndQZ97kHtpXnbdsu33uNp5ccefGGsm5wqho0neBeU8/WdeWfrzqvCXDGY+lw96C+AScTIMQAYy0rLgusdYgx8szAn0SID9BVqaxx8D7wxjEuCCHCec/yISDDL7Wy2y0RvwBn1aMoco5bkTrRo+06iH7gdfbZwV9KtTiFuiwI0vOZB3KV7sE6B2PM7cBgJSoGCnLVM2SeZ2ju+nZgdBJlAlI8qRS/jtTThSTw7UAjIU6vkAQMziLQ7Wp6apZiG73DYVASMT9gaWrEcUDUM4IcEWgddgHJYVsc0bcdpnG8iGKb+R8OTX6EJYdOCLhhYPndDieFQMDYNohDz7E5Mm0U9jwbTxB7eIarK7iuJXc0E5wdU/R3tY72ZhqkSJAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 12 05 11 29 43&quot; title=&quot;&quot; data-src=&quot;/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-fee1c.png&quot; data-srcset=&quot;/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-a67b7.png 200w,
/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-0b187.png 400w,
/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-fee1c.png 800w,
/static/2023-12-05-11-29-43-56ee4c20b0d22def0adcbc9427ce5eb8-a01e8.png 1116w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;和 &lt;a href=&quot;https://rsbuild.dev/plugins/list/plugin-assets-retry&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;plugin-assets-retry&lt;/a&gt; 插件的效果异曲同工&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Python 后端 oom 处理过程]]></title><description><![CDATA[背景 python 的 django 后端在刚启动时内存占用上升很快，导致对应的 pod 内存溢出，很多接口响应慢 方案 需要找到哪行代码导致的内存泄漏，有以下方案 pympler scalene  (python3.8 以上，不符合) memray  (python3.…]]></description><link>https://blog.towavephone.com/python-backend-oom-process/</link><guid isPermaLink="false">https://blog.towavephone.com/python-backend-oom-process/</guid><pubDate>Thu, 30 Nov 2023 17:46:51 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;python 的 django 后端在刚启动时内存占用上升很快，导致对应的 pod 内存溢出，很多接口响应慢&lt;/p&gt;
&lt;h1 id=&quot;方案&quot;&gt;&lt;a href=&quot;#%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;方案&lt;/h1&gt;
&lt;p&gt;需要找到哪行代码导致的内存泄漏，有以下方案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/436577356&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;pympler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/plasma-umass/scalene&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;scalene&lt;/a&gt; (python3.8 以上，不符合)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bloomberg/memray&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;memray&lt;/a&gt; (python3.7 以上，不符合)&lt;/li&gt;
&lt;li&gt;使用 python 内置的 tracemalloc 库&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于项目用的是 python 3.6 的版本，所以采用方案 1 和 4&lt;/p&gt;
&lt;h1 id=&quot;实现&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现&lt;/h1&gt;
&lt;h2 id=&quot;内存占用检测&quot;&gt;&lt;a href=&quot;#%E5%86%85%E5%AD%98%E5%8D%A0%E7%94%A8%E6%A3%80%E6%B5%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;内存占用检测&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;通过接口获取当前内存占用最大的排名前几的代码行数&lt;/li&gt;
&lt;li&gt;通过接口获取当前占用内存最大的对象&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;controllers/app_monitor_controller.py&lt;/code&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5704790160550277000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import tracemalloc
import logging

from controllers.wrapper import (
    get_param, get_int_param, request_wrapper)


@request_wrapper()
def get_tracemalloc(request):
    key_type = get_param(request, &apos;key_type&apos;, default=&apos;traceback&apos;)
    limit = get_int_param(request, &apos;limit&apos;, default=&apos;10&apos;)
    snapshot = tracemalloc.take_snapshot()
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, &amp;quot;&lt;unknown&gt;&amp;quot;),
        tracemalloc.Filter(
            False, &amp;quot;&lt;frozen importlib._bootstrap_external&gt;&amp;quot;),
        tracemalloc.Filter(
            False, &amp;quot;&lt;frozen importlib._bootstrap&gt;&amp;quot;),
        tracemalloc.Filter(False, tracemalloc.__file__),
    ))
    top_stats = snapshot.statistics(key_type)
    top_result = []
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        desc = &amp;quot;#%s: %s:%s: %.1f KiB&amp;quot; % (
            index, frame.filename, frame.lineno, stat.size / 1024)
        top_result.append({
            &apos;desc&apos;: desc,
            &apos;traceback&apos;: stat.traceback.format()
        })

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)

    total = sum(stat.size for stat in top_stats)

    return {
        &apos;top_result&apos;: top_result,
        &apos;other_result&apos;: &amp;quot;Total %s, other: %.1f KiB&amp;quot; %
        (len(other), size / 1024),
        &apos;total_size&apos;: &amp;quot;%.1f KiB&amp;quot; % (total / 1024)
    }


@request_wrapper()
def get_memory(request):
    limit = get_int_param(request, &apos;limit&apos;, default=&apos;1&apos;)
    from pympler import muppy, summary

    all_objects = muppy.get_objects()
    sum1 = summary.summarize(all_objects)
    logging.info(&apos;Summary -----&apos;)
    summary.print_(sum1)

    filter_all_objects = muppy.sort(all_objects)
    dicts = [ao for ao in filter_all_objects if isinstance(ao, dict)]
    result = []
    for d in dicts[-limit:]:
        item = f&apos;{len(d)}, {str(d).encode(&amp;quot;utf-8&amp;quot;)}&apos;
        result.append(item)

    import objgraph

    logging.info(&apos;Common -----&apos;)
    objgraph.show_most_common_types()

    logging.info(&apos;Growth -----&apos;)
    objgraph.show_growth(limit=5)

    return result`, `5704790160550277000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; tracemalloc
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; logging

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; controllers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wrapper &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    get_param&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; get_int_param&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request_wrapper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


@request_wrapper&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_tracemalloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    key_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_param&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;key_type&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;traceback&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_int_param&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;limit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;10&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    snapshot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;take_snapshot&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    snapshot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; snapshot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;filter_traces&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;unknown&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;frozen importlib._bootstrap_external&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;frozen importlib._bootstrap&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__file__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    top_stats &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; snapshot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statistics&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key_type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    top_result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stat &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;top_stats&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;traceback&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        desc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#%s: %s:%s: %.1f KiB&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;filename&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineno&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        top_result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;desc&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; desc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;traceback&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;traceback&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    other &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; top_stats&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; other&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; stat &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; other&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    total &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; stat &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; top_stats&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;top_result&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; top_result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;other_result&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Total %s, other: %.1f KiB&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;other&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;total_size&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%.1f KiB&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;total &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


@request_wrapper&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_memory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_int_param&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;limit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pympler &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; muppy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; summary

    all_objects &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; muppy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_objects&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    sum1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;summarize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_objects&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Summary -----&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print_&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    filter_all_objects &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; muppy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_objects&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    dicts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ao &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; ao &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; filter_all_objects &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ao&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; d &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; dicts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;encode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
        result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; objgraph

    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Common -----&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    objgraph&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;show_most_common_types&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Growth -----&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    objgraph&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;show_growth&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;limit&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里需要在项目启动的时候去开启记录内存的功能，否则上面的 &lt;code class=&quot;language-text&quot;&gt;get_tracemalloc&lt;/code&gt; 功能不可用，当然这里的功能需要在使用结束后立即关闭，否则内存占用较大&lt;/p&gt;
&lt;p&gt;apps.py&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;79743039449821900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`from django.apps import AppConfig
import tracemalloc
import os

# 减少 pypinyin 的内存占用
os.environ[&apos;PYPINYIN_NO_PHRASES&apos;] = &apos;true&apos;
os.environ[&apos;PYPINYIN_NO_DICT_COPY&apos;] = &apos;true&apos;


class Config(AppConfig):
    def ready(self):
        from entry.settings import START_RECORD_MEMORY
        if not START_RECORD_MEMORY:
            return
        tracemalloc.start(25)`, `79743039449821900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apps &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; AppConfig
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; tracemalloc
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os

&lt;span class=&quot;token comment&quot;&gt;# 减少 pypinyin 的内存占用&lt;/span&gt;
os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;PYPINYIN_NO_PHRASES&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;
os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;PYPINYIN_NO_DICT_COPY&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppConfig&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ready&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;settings &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; START_RECORD_MEMORY
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; START_RECORD_MEMORY&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
        tracemalloc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;耗时检测&quot;&gt;&lt;a href=&quot;#%E8%80%97%E6%97%B6%E6%A3%80%E6%B5%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;耗时检测&lt;/h2&gt;
&lt;p&gt;主要用来统计函数执行过程中每行耗时及调用次数&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;tools/performance_tool.py&lt;/code&gt;&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;36466925011673125000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`from cProfile import Profile
from pstats import Stats


def print_func_cost():
    def wrapper2(func):
        def wrapper1(*args, **kwargs):
            profiler = Profile()
            # request = args[0]

            result = profiler.runcall(func, *args, **kwargs)

            stats = Stats(profiler)
            stats.strip_dirs()
            stats.sort_stats(&apos;cumulative&apos;)
            # stats.print_stats()
            stats.print_callers()

            return result
        return wrapper1
    return wrapper2`, `36466925011673125000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;py&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;py&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;py&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-py line-numbers&quot;&gt;&lt;code class=&quot;language-py&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; cProfile &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Profile
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pstats &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Stats


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;print_func_cost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrapper1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            profiler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Profile&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# request = args[0]&lt;/span&gt;

            result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; profiler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runcall&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            stats &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Stats&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;profiler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            stats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strip_dirs&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            stats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort_stats&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;cumulative&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;# stats.print_stats()&lt;/span&gt;
            stats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print_callers&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper1
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; wrapper2&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id=&quot;效果展示&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果展示&lt;/h1&gt;
&lt;p&gt;由以下日志可看出，内存占用较大的部分主要在 protobuf 解码，大文件读取，pypinyin，之后的优化就可以进行下去&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34569475462063680000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`07:56:48 apps.py:37 INFO #1: /usr/local/lib/python3.6/site-packages/google/protobuf/text_format.py:682: 114541.4 KiB
07:56:48 apps.py:43 INFO     b&apos;  File &amp;quot;文件路径&amp;quot;, line 1580&apos;
07:56:48 apps.py:43 INFO     b&apos;    data = text_format.Parse(data, DataTree())&apos;
07:00:05 apps.py:33 INFO #2: 文件路径:1571: 66375.2 KiB
07:00:05 apps.py:37 INFO     b&amp;quot;data = data + open(file_path, &apos;r&apos;).read()&amp;quot;
07:00:05 apps.py:33 INFO #8: 文件路径:1569: 2053.4 KiB
07:00:05 apps.py:37 INFO     b&amp;quot;data = data + open(file_path, &apos;r&apos;).read()&amp;quot;
07:56:52 apps.py:37 INFO #5: /usr/local/lib/python3.6/site-packages/pypinyin/phrases_dict.py:45476: 2560.4 KiB
  07:56:52 apps.py:43 INFO     b&apos;  File &amp;quot;文件路径&amp;quot;, line 26&apos;
07:56:52 apps.py:43 INFO     b&apos;    from pypinyin import lazy_pinyin&apos;`, `34569475462063680000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;07:56:48 apps.py:37 INFO &lt;span class=&quot;token comment&quot;&gt;#1: /usr/local/lib/python3.6/site-packages/google/protobuf/text_format.py:682: 114541.4 KiB&lt;/span&gt;
07:56:48 apps.py:43 INFO     b&lt;span class=&quot;token string&quot;&gt;&apos;  File &quot;文件路径&quot;, line 1580&apos;&lt;/span&gt;
07:56:48 apps.py:43 INFO     b&lt;span class=&quot;token string&quot;&gt;&apos;    data = text_format.Parse(data, DataTree())&apos;&lt;/span&gt;
07:00:05 apps.py:33 INFO &lt;span class=&quot;token comment&quot;&gt;#2: 文件路径:1571: 66375.2 KiB&lt;/span&gt;
07:00:05 apps.py:37 INFO     b&lt;span class=&quot;token string&quot;&gt;&quot;data = data + open(file_path, &apos;r&apos;).read()&quot;&lt;/span&gt;
07:00:05 apps.py:33 INFO &lt;span class=&quot;token comment&quot;&gt;#8: 文件路径:1569: 2053.4 KiB&lt;/span&gt;
07:00:05 apps.py:37 INFO     b&lt;span class=&quot;token string&quot;&gt;&quot;data = data + open(file_path, &apos;r&apos;).read()&quot;&lt;/span&gt;
07:56:52 apps.py:37 INFO &lt;span class=&quot;token comment&quot;&gt;#5: /usr/local/lib/python3.6/site-packages/pypinyin/phrases_dict.py:45476: 2560.4 KiB&lt;/span&gt;
  07:56:52 apps.py:43 INFO     b&lt;span class=&quot;token string&quot;&gt;&apos;  File &quot;文件路径&quot;, line 26&apos;&lt;/span&gt;
07:56:52 apps.py:43 INFO     b&lt;span class=&quot;token string&quot;&gt;&apos;    from pypinyin import lazy_pinyin&apos;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[记一次处理高德地图浏览器兼容性问题]]></title><description><![CDATA[背景 在 chrome 浏览器上，用高德地图的 CircleMarker, PathSimplifier 分别画点和线时候，需要实现点在线的上面的效果，此时在 windows 系统上不能正常显示红点（左侧），在 ubuntu…]]></description><link>https://blog.towavephone.com/amap-browser-compatibility/</link><guid isPermaLink="false">https://blog.towavephone.com/amap-browser-compatibility/</guid><pubDate>Tue, 28 Nov 2023 16:55:48 GMT</pubDate><content:encoded>&lt;h1 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h1&gt;
&lt;p&gt;在 chrome 浏览器上，用高德地图的 CircleMarker, PathSimplifier 分别画点和线时候，需要实现点在线的上面的效果，此时在 windows 系统上不能正常显示红点（左侧），在 ubuntu 系统下却正常显示（右侧），如下图所示&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-11-28-17-00-49-e34e41355789ea9f86d92dfbd0971c55-16a51.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 324px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 44.1358024691358%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAACWElEQVQozz2SWU9TARCF77/0RR80MYawxBaUfW2lUiWllKVFKQSoFJTFaqkgmwgCSgCxRaCAQpe7dW8pxWg+ryHx4cuZzGTOyzlCbVMdhkcN2LvMWNtqsD+pZKTjAVNd5czadXwermKr7y6Hcw7S2QyZeJRUXCKhRokrETIphWxaJZtSSSdlBHHeyvG7DvZ9NjZetrHoMuFxGnH3NDLUWceYw4jFUMKEu5/LyxzpXJxUOkYsoaAmZLL5lEaafCFLrpBByL9vhBUDrLfBjg32nXA0BieTcOrR1AvBaXIna6iiSDawQ/IwQOLAr6lf0z2SR37tHiAb/IbwuKkMU0MRlpZi7KZSBs3FvOmtYNFZxdpQLYFJI2FPE/KuR3s+JOQaJNFjIu9oIGer5sKi4+KpnrzpHvmamwjSro/gRzfbvj5WxtvxOpt57WxlpKuBbpMOi1FP08PbjL54RuHnKT+e20m2V3PpqCc/YKQwZKbg7uRqysHVmA3h98kynH8AcR3ULxqb2rwKZwtw7OPq+1uyWy7EvXnCx0HU7Q3kT0soy3PE15cR/XtE9veRAgEiAT+CuUXPgK2R/vYKRq0VeB2VrA7X8/WVgbOZNmJLVn5t2skdLBAJnRFSRM4ViZAqEY7LnMcVQhqSRlgLSjgaKeHIXc6msxRvdxnDFj09WnX+VchqrqO1WU9LbRETY04y6SQxKUxCiV6jilp1oihyhJh8vRM6zLW4LPfZGNAhjxfB9C2YvMGfqTtczFSSnGvldLSEw9leEqk0STVCMib9J66ZKpqZLEWQxBB/AQBvSOSbuQBPAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 11 28 17 00 49&quot; title=&quot;&quot; data-src=&quot;/static/2023-11-28-17-00-49-e34e41355789ea9f86d92dfbd0971c55-16a51.png&quot; data-srcset=&quot;/static/2023-11-28-17-00-49-e34e41355789ea9f86d92dfbd0971c55-6752f.png 200w,
/static/2023-11-28-17-00-49-e34e41355789ea9f86d92dfbd0971c55-16a51.png 324w&quot; data-sizes=&quot;(max-width: 324px) 100vw, 324px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h1 id=&quot;原因&quot;&gt;&lt;a href=&quot;#%E5%8E%9F%E5%9B%A0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;原因&lt;/h1&gt;
&lt;p&gt;红点是由 &lt;a href=&quot;https://lbs.amap.com/api/javascript-api-v2/documentation#circlemarker&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;CircleMarker&lt;/a&gt; 组件实现，而下面的线则是 &lt;a href=&quot;https://lbs.amap.com/api/amap-ui/reference-amap-ui/mass-data/pathsimplifier&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;PathSimplifier&lt;/a&gt; 实现，在 chrome 浏览器下不同系统的表现为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;window: CircleMarker 由 canvas 渲染，且层级和高德地图图层一致，而 PathSimplifier 的层级比高德地图图层要高&lt;/li&gt;
&lt;li&gt;ubuntu: CircleMarker 由 svg 渲染，层级比 PathSimplifier 要高&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;优缺点&quot;&gt;&lt;a href=&quot;#%E4%BC%98%E7%BC%BA%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;优缺点&lt;/h1&gt;
&lt;p&gt;综上所述，需要统一不同系统下的渲染方式，优缺点对比如下&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;渲染方式&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;优点&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;canvas&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;性能好&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;实现成本高，即需要生成比 PathSimplifier 所在层级要高的新图层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;svg&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;实现方便，有现成的 
&lt;a href=&quot;https://lbs.amap.com/api/amap-ui/reference-amap-ui/overlay/svgmarker&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;SvgMarker&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;性能差，但对于这个场景来说只需要鼠标悬浮上去才渲染一条路线上的点，即性能要求不高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最终采用 SvgMarker 渲染&lt;/p&gt;
&lt;h1 id=&quot;效果展示&quot;&gt;&lt;a href=&quot;#%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;效果展示&lt;/h1&gt;
&lt;p&gt;以下都在 chrome 浏览器下测试：&lt;/p&gt;
&lt;h2 id=&quot;修复前&quot;&gt;&lt;a href=&quot;#%E4%BF%AE%E5%A4%8D%E5%89%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;修复前&lt;/h2&gt;
&lt;p&gt;windows 下不能正常显示，ubuntu 正常&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;iframe data-src=&quot;https://codesandbox.io/embed/x86xtf?codemirror=1&amp;amp;hidenavigation=1&amp;amp;module=%2Fsrc%2Fcomponents%2FReactAmap%2Fhooks%2FuseDrawPoints.js&amp;amp;theme=light&amp;amp;view=preview&quot; class=&quot;embedded-codesandbox lazy&quot; sandbox=&quot;allow-modals allow-forms allow-popups allow-scripts allow-same-origin allow-downloads&quot;&gt;&lt;/iframe&gt;&lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&quot;修复后&quot;&gt;&lt;a href=&quot;#%E4%BF%AE%E5%A4%8D%E5%90%8E&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;修复后&lt;/h2&gt;
&lt;p&gt;windows、ubuntu 都能正常显示&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;iframe data-src=&quot;https://codesandbox.io/embed/9cpd4v?codemirror=1&amp;amp;hidenavigation=1&amp;amp;module=%2Fsrc%2Fcomponents%2FReactAmap%2Fhooks%2FuseDrawPoints.js&amp;amp;theme=light&amp;amp;view=preview&quot; class=&quot;embedded-codesandbox lazy&quot; sandbox=&quot;allow-modals allow-forms allow-popups allow-scripts allow-same-origin allow-downloads&quot;&gt;&lt;/iframe&gt;&lt;/body&gt;&lt;/html&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[博客 Gatsby 插件改造]]></title><description><![CDATA[需求背景 针对本博客现有的 Gatsby 框架实现代码复制、代码实时查看编辑的功能 技术选型 以下都是采用最后一种方案，由于插件功能不满足，所以对其源码做出改动 代码复制 技术选型 语法 优点 缺点 项目代码自实现 未实现 定制化程度高 实现难度大； 拓展较差 gatsby…]]></description><link>https://blog.towavephone.com/gatsby-plugin-transformation/</link><guid isPermaLink="false">https://blog.towavephone.com/gatsby-plugin-transformation/</guid><pubDate>Tue, 28 Nov 2023 16:17:27 GMT</pubDate><content:encoded>&lt;h1 id=&quot;需求背景&quot;&gt;&lt;a href=&quot;#%E9%9C%80%E6%B1%82%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;需求背景&lt;/h1&gt;
&lt;p&gt;针对本博客现有的 Gatsby 框架实现代码复制、代码实时查看编辑的功能&lt;/p&gt;
&lt;h1 id=&quot;技术选型&quot;&gt;&lt;a href=&quot;#%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;技术选型&lt;/h1&gt;
&lt;p&gt;以下都是采用最后一种方案，由于插件功能不满足，所以对其源码做出改动&lt;/p&gt;
&lt;h2 id=&quot;代码复制&quot;&gt;&lt;a href=&quot;#%E4%BB%A3%E7%A0%81%E5%A4%8D%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;代码复制&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;技术选型&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;语法&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;优点&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;项目代码自实现&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;未实现&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;定制化程度高&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;ol&gt;
&lt;li&gt;
实现难度大；
&lt;/li&gt;
&lt;li&gt;
拓展较差
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/iamskok/gatsby-remark-code-buttons&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;gatsby-remark-code-buttons&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;不需特定语法，只要是代码块默认具有复制功能&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;已具有代码复制功能&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;ol&gt;
&lt;li&gt;
有多复制一行的 bug；
&lt;/li&gt;
&lt;li&gt;
UI 不符合博客主题
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;代码实时查看编辑&quot;&gt;&lt;a href=&quot;#%E4%BB%A3%E7%A0%81%E5%AE%9E%E6%97%B6%E6%9F%A5%E7%9C%8B%E7%BC%96%E8%BE%91&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;代码实时查看编辑&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;技术选型&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;语法&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;优点&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;缺点&lt;/th&gt;
&lt;th&gt;样例页面&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;iframe + 
&lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-embed-snippet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;gatsby-remark-embed-snippet&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;iframe src=&amp;quot;/examples/snow.html&amp;quot; width=&amp;quot;800&amp;quot; height=&amp;quot;400&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;
 
&lt;br /&gt;
 
&lt;code class=&quot;language-text&quot;&gt;`embed:snow.html`&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;实现最为简单，容易出效果&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;ol&gt;
&lt;li&gt;
没有实时编辑功能；
&lt;li&gt;
嵌入的代码往往需要将其写成一个 html 文件且一般情况下只支持静态页面的展示；
&lt;/li&gt;
&lt;li&gt;
需要 iframe、embed 标签声明两次
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/snow-css/&quot;&gt;下雪特效&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;iframe + code-editor.html 传参&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;iframe src=&amp;quot;/examples/code-editor.html?html=转义字符串&amp;amp;css=转义字符串&amp;amp;js=转义字符串&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;基本实现代码的实时查看编辑&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;ol&gt;
&lt;li&gt;
嵌入的代码只支持静态的 html 页面；
&lt;/li&gt;
&lt;li&gt;
需要明确拆分出 js、css、html 三个结构；
&lt;/li&gt;
&lt;li&gt;
嵌入的 js、css、html 分别需要手动 escape 加密
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/css-world-four-kinds-of-box/#border-%E7%AD%89%E9%AB%98%E5%B8%83%E5%B1%80%E6%8A%80%E6%9C%AF&quot;&gt;CSS 世界四大盒尺寸&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/elboman/gatsby-remark-embedded-codesandbox&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;gatsby-remark-embedded-codesandbox&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[pms-example](embedded-codesandbox://gantt-component-optimization/pms-example)&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;借助 codesandbox 实现了较为丰富的代码实时查看编辑功能&lt;/td&gt;
&lt;td align=&quot;left&quot;&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;
会上传 node_modules 等等大文件
&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;td&gt;需实现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&quot;核心逻辑&quot;&gt;&lt;a href=&quot;#%E6%A0%B8%E5%BF%83%E9%80%BB%E8%BE%91&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;核心逻辑&lt;/h1&gt;
&lt;h2 id=&quot;代码复制-1&quot;&gt;&lt;a href=&quot;#%E4%BB%A3%E7%A0%81%E5%A4%8D%E5%88%B6-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;代码复制&lt;/h2&gt;
&lt;h3 id=&quot;修复复制多一行-bug&quot;&gt;&lt;a href=&quot;#%E4%BF%AE%E5%A4%8D%E5%A4%8D%E5%88%B6%E5%A4%9A%E4%B8%80%E8%A1%8C-bug&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;修复复制多一行 bug&lt;/h3&gt;
&lt;p&gt;代码在&lt;a href=&quot;https://github.com/towavephone/gatsby-remark-code-buttons/commit/d95bb194d74182a3c5b8118102b3826695c2ff6d&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;src/gatsby-browser.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;98834764866875500000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@@ -7,19 +7,9 @@ exports.onClientEntry = () =&gt; {
    el.innerHTML = str;
    document.body.appendChild(el);

-   const range = document.createRange();
-   range.selectNode(el);
-   window.getSelection().removeAllRanges();
-   window.getSelection().addRange(range);
-   document.execCommand(\`copy\`);
-   document.activeElement.blur();
-   setTimeout(() =&gt; {
-     document.getSelection().removeAllRanges();
-     document.body.removeChild(el);
-   }, 100);
+   el.select();
+   document.execCommand(&amp;quot;copy&amp;quot;);
+   document.body.removeChild(el);
    if (toasterId) {
      window.showClipboardToaster(toasterId);
    }`, `98834764866875500000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;diff&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;diff&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-diff line-numbers&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;@@ -7,19 +7,9 @@ exports.onClientEntry = () =&gt; {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   el.innerHTML = str;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   document.body.appendChild(el);
&lt;/span&gt;
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   const range = document.createRange();
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   range.selectNode(el);
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   window.getSelection().removeAllRanges();
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   window.getSelection().addRange(range);
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   document.execCommand(`copy`);
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   document.activeElement.blur();
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   setTimeout(() =&gt; {
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;     document.getSelection().removeAllRanges();
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;     document.body.removeChild(el);
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   }, 100);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   el.select();
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   document.execCommand(&quot;copy&quot;);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   document.body.removeChild(el);
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   if (toasterId) {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     window.showClipboardToaster(toasterId);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   }&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;增加显示语言类型的功能；改变按钮文本显示逻辑；任何语言都可以显示复制按钮&quot;&gt;&lt;a href=&quot;#%E5%A2%9E%E5%8A%A0%E6%98%BE%E7%A4%BA%E8%AF%AD%E8%A8%80%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%8A%9F%E8%83%BD%EF%BC%9B%E6%94%B9%E5%8F%98%E6%8C%89%E9%92%AE%E6%96%87%E6%9C%AC%E6%98%BE%E7%A4%BA%E9%80%BB%E8%BE%91%EF%BC%9B%E4%BB%BB%E4%BD%95%E8%AF%AD%E8%A8%80%E9%83%BD%E5%8F%AF%E4%BB%A5%E6%98%BE%E7%A4%BA%E5%A4%8D%E5%88%B6%E6%8C%89%E9%92%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;增加显示语言类型的功能；改变按钮文本显示逻辑；任何语言都可以显示复制按钮&lt;/h3&gt;
&lt;p&gt;主要针对原插件做出的一些 UI、功能上的适配，代码在&lt;a href=&quot;https://github.com/towavephone/gatsby-remark-code-buttons/commit/90094cd1c6a4a6fa7253f61e898f8a832173d6a9&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;src/index.js&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;96965905009398070000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@@ -21,10 +21,6 @@ module.exports = function gatsbyRemarkCodeButtons(
    const actions = qs.parse(params);
    const { clipboard } = actions;

-   if (!language) {
-     return;
-   }
-
    if (clipboard === &apos;false&apos;) {
      delete actions[&apos;clipboard&apos;];
    } else {
@@ -57,12 +53,12 @@ module.exports = function gatsbyRemarkCodeButtons(
            &gt;
              &lt;div
                class=&amp;quot;\${buttonClass}&amp;quot;
-               data-tooltip=&amp;quot;\${tooltipText}&amp;quot;
+               \${tooltipText ? \`data-tooltip=&amp;quot;\${tooltipText}&amp;quot;\` : &apos;&apos;}
              &gt;
-               \${buttonText}\${svgIcon}
+               \${[language, buttonText || svgIcon].filter((item) =&gt; item).join(&apos; &apos;)}
              &lt;/div&gt;
            &lt;/div&gt;
-           \`.trim()
+         \`.trim()
      };

      parent.children.splice(index, 0, buttonNode);`, `96965905009398070000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;diff&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;diff&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-diff line-numbers&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;@@ -21,10 +21,6 @@ module.exports = function gatsbyRemarkCodeButtons(
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   const actions = qs.parse(params);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   const { clipboard } = actions;
&lt;/span&gt;
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   if (!language) {
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;     return;
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   }
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   if (clipboard === &apos;false&apos;) {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     delete actions[&apos;clipboard&apos;];
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   } else {
&lt;/span&gt;@@ -57,12 +53,12 @@ module.exports = function gatsbyRemarkCodeButtons(
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;           &gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;             &amp;lt;div
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;               class=&quot;${buttonClass}&quot;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;               data-tooltip=&quot;${tooltipText}&quot;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;               ${tooltipText ? `data-tooltip=&quot;${tooltipText}&quot;` : &apos;&apos;}
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;             &gt;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;               ${buttonText}${svgIcon}
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;               ${[language, buttonText || svgIcon].filter((item) =&gt; item).join(&apos; &apos;)}
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;             &amp;lt;/div&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;           &amp;lt;/div&gt;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;           `.trim()
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         `.trim()
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     };
&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     parent.children.splice(index, 0, buttonNode);&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;实现效果&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现效果&lt;/h3&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27762516532137440000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 测试代码复制功能，这个代码块应该具有复制代码功能`, `27762516532137440000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 测试代码复制功能，这个代码块应该具有复制代码功能&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;代码实时查看编辑-1&quot;&gt;&lt;a href=&quot;#%E4%BB%A3%E7%A0%81%E5%AE%9E%E6%97%B6%E6%9F%A5%E7%9C%8B%E7%BC%96%E8%BE%91-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;代码实时查看编辑&lt;/h2&gt;
&lt;p&gt;代码在&lt;a href=&quot;https://github.com/towavephone/gatsby-remark-embedded-codesandbox/commit/6a1947c22566c6ad5baae33dabf99edd16f4c8eb&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;递归遍历文件夹；增加忽略文件功能&quot;&gt;&lt;a href=&quot;#%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86%E6%96%87%E4%BB%B6%E5%A4%B9%EF%BC%9B%E5%A2%9E%E5%8A%A0%E5%BF%BD%E7%95%A5%E6%96%87%E4%BB%B6%E5%8A%9F%E8%83%BD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;递归遍历文件夹；增加忽略文件功能&lt;/h3&gt;
&lt;p&gt;原插件不支持目录的读取，这里拓展了目录下也能递归读取的功能，同时也实现了默认忽略文件的功能&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;82365241989429260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const DEFAULT_IGNORED_FILES = [&apos;node_modules&apos;, &apos;package-lock.json&apos;, &apos;yarn.lock&apos;];

const ignoredFilesSet = new Set(ignoredFiles);

const getAllFiles = (dirPath) =&gt;
   fs.readdirSync(dirPath).reduce((acc, file) =&gt; {
      // 过滤文件立即跳出下一个
      if (ignoredFilesSet.has(file)) return acc;
      const relativePath = path.join(dirPath, file);
      const isDirectory = fs.statSync(relativePath).isDirectory();
      // 判断是目录继续递归遍历
      const additions = isDirectory ? getAllFiles(relativePath) : [relativePath.replace(\`\${directory}/\`, &apos;&apos;)];
      return [...acc, ...additions];
   }, []);`, `82365241989429260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_IGNORED_FILES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;node_modules&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;package-lock.json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;yarn.lock&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ignoredFilesSet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ignoredFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getAllFiles&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dirPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
   fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 过滤文件立即跳出下一个&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ignoredFilesSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; relativePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isDirectory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;statSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relativePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 判断是目录继续递归遍历&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; additions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isDirectory &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAllFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relativePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;relativePath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;directory&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;additions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;默认模式为静态服务器&quot;&gt;&lt;a href=&quot;#%E9%BB%98%E8%AE%A4%E6%A8%A1%E5%BC%8F%E4%B8%BA%E9%9D%99%E6%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;默认模式为静态服务器&lt;/h3&gt;
&lt;p&gt;因为 codesandbox 需要明确模板类型，这里默认为静态模板，否则静态文件不能正常运行&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53940176812115650000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const getFileExist = (fileList, filename = &apos;package.json&apos;) =&gt; {
   const found = fileList.filter((name) =&gt; name === filename);
   return found.length &gt; null;
};

if (!getFileExist(folderFiles, &apos;sandbox.config.json&apos;)) {
   sandboxFiles.push({
      name: &apos;sandbox.config.json&apos;,
      content: &apos;{ &amp;quot;template&amp;quot;: &amp;quot;static&amp;quot; }&apos;
   });
}`, `53940176812115650000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; getFileExist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileList&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;package.json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; found &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fileList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; found&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileExist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;folderFiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sandbox.config.json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   sandboxFiles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sandbox.config.json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      content&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{ &quot;template&quot;: &quot;static&quot; }&apos;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;codesandbox-请求方式改为-post-异步化&quot;&gt;&lt;a href=&quot;#codesandbox-%E8%AF%B7%E6%B1%82%E6%96%B9%E5%BC%8F%E6%94%B9%E4%B8%BA-post-%E5%BC%82%E6%AD%A5%E5%8C%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;codesandbox 请求方式改为 post 异步化&lt;/h3&gt;
&lt;p&gt;原插件是基于 get 请求的，一旦上传文件过多就会报错，这里改为 post 请求就不再有请求体大小的限制，同时也要注意 post 的异步&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;64808230624136900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`const convertNodeToEmbedded = async (node, params, options = {}) =&gt; {
   delete node.children;
   delete node.position;
   delete node.title;
   delete node.url;

   // merge the overriding options with the plugin one
   const mergedOptions = { ...embedOptions, ...options };
   const encodedEmbedOptions = queryString.stringify(mergedOptions);

   const { sandbox_id } = await fetch(&apos;https://codesandbox.io/api/v1/sandboxes/define?json=1&apos;, {
      method: &apos;POST&apos;,
      headers: {
         &apos;Content-Type&apos;: &apos;application/json&apos;,
         Accept: &apos;application/json&apos;
      },
      body: params
   }).then((x) =&gt; x.json());

   const sandboxUrl = \`https://codesandbox.io/embed/\${sandbox_id}?\${encodedEmbedOptions}\`;
   const embedded = getIframe(sandboxUrl);
   node.type = &apos;html&apos;;
   node.value = embedded;

   return node;
};

const nodes = [];
map(markdownAST, (node, index, parent) =&gt; {
   if (node.type === &apos;link&apos; &amp;&amp; node.url.startsWith(protocol)) {
      // split the url in base and query to allow user
      // to customise embedding options on a per-node basis
      const url = getUrlParts(node.url);
      // get all files in the folder and generate
      // the embeddeing parameters
      const dir = getDirectoryPath(url.base);
      const files = getFilesList(dir);
      const params = createParams(files);
      convertNodeToEmbedded(node, params, url.query);
      const currentNode = convertNodeToEmbedded(node, params, url.query);
      nodes.push(currentNode);
   }

   return node;
});

// 注意这里的异步化，必须等所有的请求成功响应才可继续遍历
await Promise.all(nodes);`, `64808230624136900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;js&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;js&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-js line-numbers&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;convertNodeToEmbedded&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token comment&quot;&gt;// merge the overriding options with the plugin one&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mergedOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;embedOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; encodedEmbedOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queryString&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mergedOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sandbox_id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;https://codesandbox.io/api/v1/sandboxes/define?json=1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      method&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      headers&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         Accept&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; params
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sandboxUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://codesandbox.io/embed/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;sandbox_id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;encodedEmbedOptions&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; embedded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getIframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sandboxUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; embedded&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nodes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdownAST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;link&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;protocol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// split the url in base and query to allow user&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// to customise embedding options on a per-node basis&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUrlParts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// get all files in the folder and generate&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// the embeddeing parameters&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDirectoryPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;base&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFilesList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;convertNodeToEmbedded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertNodeToEmbedded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 注意这里的异步化，必须等所有的请求成功响应才可继续遍历&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nodes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;实现效果-1&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现效果&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/gantt-component-optimization/#%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C&quot;&gt;甘特图组件源码优化&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;后续优化&quot;&gt;&lt;a href=&quot;#%E5%90%8E%E7%BB%AD%E4%BC%98%E5%8C%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;后续优化&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/elboman/gatsby-remark-embedded-codesandbox/compare/master...towavephone:gatsby-remark-embedded-codesandbox:master&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;代码地址&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增加上传文件忽略功能，支持 post 上传文件&lt;/li&gt;
&lt;li&gt;修复 json 解析报错&lt;/li&gt;
&lt;li&gt;限制请求并发防止 codesandbox 报错&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;总结&quot;&gt;&lt;a href=&quot;#%E6%80%BB%E7%BB%93&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;总结&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;复制功能需考虑兼容性、复制性能，可参考&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2021/10/js-copy-paste-clipboard/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;JS 复制文字到剪切板的极简实现及扩展&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;代码实时查看编辑借助于第三方服务不够稳定，可自行搭建第三方服务或者参照 &lt;a href=&quot;https://github.com/gfxfundamentals/live-editor&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;live-editor&lt;/a&gt; 自建本地编辑器，必要时具有分享功能&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;其他优化&quot;&gt;&lt;a href=&quot;#%E5%85%B6%E4%BB%96%E4%BC%98%E5%8C%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;其他优化&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;把 gatsby-remark-graph 配置化，以支持最新的 mermaid 特性，&lt;a href=&quot;https://github.com/konsumer/gatsby-remark-graph/compare/master...towavephone:gatsby-remark-graph-towavephone:master&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;代码地址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;gatsby-remark-design-system 支持文字转语音（利用百度翻译），&lt;a href=&quot;https://github.com/LekoArts/gatsby-remark-design-system/compare/master...towavephone:gatsby-remark-design-system-towavephone:master&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;代码地址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;img、video、iframe 增加懒加载功能，&lt;a href=&quot;https://github.com/1kohei1/gatsby-remark-lazy-load/compare/master...towavephone:gatsby-remark-lazy-load:master&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;代码地址&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[Go 入门学习]]></title><description><![CDATA[入门 Hello, World 我们以现已成为传统的   案例来开始吧 Go 是一门编译型语言，Go 语言的工具链将源代码及其依赖转换成计算机的机器指令（译注：静态编译）。Go 语言提供的工具都通过一个单独的命令 go 调用，go…]]></description><link>https://blog.towavephone.com/go-introduce-learn/</link><guid isPermaLink="false">https://blog.towavephone.com/go-introduce-learn/</guid><pubDate>Mon, 02 Oct 2023 17:58:46 GMT</pubDate><content:encoded>&lt;h1 id=&quot;入门&quot;&gt;&lt;a href=&quot;#%E5%85%A5%E9%97%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;入门&lt;/h1&gt;
&lt;h2 id=&quot;hello-world&quot;&gt;&lt;a href=&quot;#hello-world&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hello, World&lt;/h2&gt;
&lt;p&gt;我们以现已成为传统的 &lt;code class=&quot;language-text&quot;&gt;hello world&lt;/code&gt; 案例来开始吧&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34375652576104997000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`package main

import &amp;quot;fmt&amp;quot;

func main() {
    fmt.Println(&amp;quot;Hello, 世界&amp;quot;)
}`, `34375652576104997000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; main

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, 世界&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Go 是一门编译型语言，Go 语言的工具链将源代码及其依赖转换成计算机的机器指令（译注：静态编译）。Go 语言提供的工具都通过一个单独的命令 go 调用，go 命令有一系列子命令。最简单的一个子命令就是 run。这个命令编译一个或多个以 .go 结尾的源文件，链接库文件，并运行最终生成的可执行文件（本书使用 &lt;code class=&quot;language-text&quot;&gt;$&lt;/code&gt; 表示命令行提示符。）&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69612157252979155000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`\$ go run helloworld.go`, `69612157252979155000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go run helloworld.go&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;毫无意外，这个命令会输出：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;64947977057906470000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Hello, 世界`, `64947977057906470000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Hello, 世界&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Go 语言原生支持 Unicode，它可以处理全世界任何语言的文本。&lt;/p&gt;
&lt;p&gt;如果不只是一次性实验，你肯定希望能够编译这个程序，保存编译结果以备将来之用，可以用 build 子命令：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;98993884990158720000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`\$ go build helloworld.go`, `98993884990158720000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go build helloworld.go&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个命令生成一个名为 helloworld 的可执行的二进制文件（译注：Windows 系统下生成的可执行文件是 helloworld.exe，增加了 .exe 后缀名），之后你可以随时运行它（译注：在 Windows 系统下在命令行直接输入 helloworld.exe 命令运行），不需任何处理（译注：因为静态编译，所以不用担心在系统库更新的时候冲突，幸福感满满）。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88089078976067620000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`\$ ./helloworld
Hello, 世界`, `88089078976067620000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ./helloworld
Hello, 世界&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;执行 &lt;code class=&quot;language-text&quot;&gt;go get gopl.io/ch1/helloworld&lt;/code&gt; 命令，就会从网上获取代码，并放到对应目录中（需要先安装 Git 或 Hg 之类的版本管理工具，并将对应的命令添加到 PATH 环境变量中。&lt;/p&gt;
&lt;p&gt;来讨论下程序本身。Go 语言的代码通过包（package）组织，包类似于其它语言里的库（libraries）或者模块（modules）。一个包由位于单个目录下的一个或多个 .go 源代码文件组成，目录定义包的作用。每个源文件都以一条 package 声明语句开始，这个例子里就是 package main，表示该文件属于哪个包，紧跟着一系列导入（import）的包，之后是存储在这个文件里的程序语句。&lt;/p&gt;
&lt;p&gt;Go 的标准库提供了 100 多个包，以支持常见功能，如输入、输出、排序以及文本处理。比如 fmt 包，就含有格式化输出、接收输入的函数。Println 是其中一个基础函数，可以打印以空格间隔的一个或多个值，并在最后添加一个换行符，从而输出一整行。&lt;/p&gt;
&lt;p&gt;main 包比较特殊。它定义了一个独立可执行的程序，而不是一个库。在 main 里的 main 函数也很特殊，它是整个程序执行时的入口（译注：C 系语言差不多都这样）。main 函数所做的事情就是程序做的。当然了，main 函数一般调用其它包里的函数完成很多工作（如：fmt.Println）。&lt;/p&gt;
&lt;p&gt;必须告诉编译器源文件需要哪些包，这就是跟随在 package 声明后面的 import 声明扮演的角色。hello world 例子只用到了一个包，大多数程序需要导入多个包。&lt;/p&gt;
&lt;p&gt;必须恰当导入需要的包，缺少了必要的包或者导入了不需要的包，程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包（译注：Go 语言编译过程没有警告信息，争议特性之一）。&lt;/p&gt;
&lt;p&gt;import 声明必须跟在文件的 package 声明之后。随后，则是组成程序的函数、变量、常量、类型的声明语句（分别由关键字 func、var、const、type 定义）。这些内容的声明顺序并不重要（译注：最好还是定一下规范）。这个例子的程序已经尽可能短了，只声明了一个函数，其中只调用了一个其他函数。为了节省篇幅，有些时候示例程序会省略 package 和 import 声明，但是，这些声明在源代码里有，并且必须得有才能编译。&lt;/p&gt;
&lt;p&gt;一个函数的声明由 func 关键字、函数名、参数列表、返回值列表（这个例子里的 main 函数参数列表和返回值都是空的）以及包含在大括号里的函数体组成。第五章进一步考察函数。&lt;/p&gt;
&lt;p&gt;Go 语言不需要在语句或者声明的末尾添加分号，除非一行上有多条语句。实际上，编译器会主动把特定符号后的换行符转换为分号，因此换行符添加的位置会影响 Go 代码的正确解析（译注：比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字 break、continue、fallthrough 或 return 中的一个、运算符和分隔符 &lt;code class=&quot;language-text&quot;&gt;++&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;--&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;)&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;]&lt;/code&gt; 或 &lt;code class=&quot;language-text&quot;&gt;}&lt;/code&gt; 中的一个）。举个例子，函数的左括号 &lt;code class=&quot;language-text&quot;&gt;{&lt;/code&gt; 必须和 func 函数声明在同一行上，且位于末尾，不能独占一行，而在表达式 &lt;code class=&quot;language-text&quot;&gt;x + y&lt;/code&gt; 中，可在 + 后换行，不能在 + 前换行（译注：以 + 结尾的话不会被插入分号分隔符，但是以 x 结尾的话则会被分号分隔符，从而导致编译错误）。&lt;/p&gt;
&lt;p&gt;Go 语言在代码格式上采取了很强硬的态度。gofmt 工具把代码格式化为标准格式（译注：这个格式化工具没有任何可以调整代码格式的参数，Go 语言就是这么任性），并且 go 工具中的 fmt 子命令会对指定包，否则默认为当前目录中所有 go 源文件应用 gofmt 命令。本书中的所有代码都被 gofmt 过。你也应该养成格式化自己的代码的习惯。以固定方式规定标准的代码格式可以避免无尽的无意义的琐碎争执（译注：也导致了 Go 语言的 TIOBE 排名较低，因为缺少撕逼的话题）。更重要的是，这样可以做多种自动源码转换，如果放任 Go 语言代码格式，这些转换就不大可能了。&lt;/p&gt;
&lt;p&gt;很多文本编辑器都可以配置为保存文件时自动执行 gofmt，这样你的源代码总会被恰当地格式化。还有个相关的工具：goimports，可以根据代码需要，自动地添加或删除 import 声明。这个工具并没有包含在标准的分发包中，可以用下面的命令安装：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;98577250882072150000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`\$ go get golang.org/x/tools/cmd/goimports`, `98577250882072150000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ go get golang.org/x/tools/cmd/goimports&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于大多数用户来说，下载、编译包、运行测试用例、察看 Go 语言的文档等等常用功能都可以用 go 的工具完成。10.7 节详细介绍这些知识。&lt;/p&gt;
&lt;h2 id=&quot;命令行参数&quot;&gt;&lt;a href=&quot;#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;命令行参数&lt;/h2&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;68341238369279100000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// Echo1 prints its command-line arguments.
package main

import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;os&amp;quot;
)

func main() {
    var s, sep string
    for i := 1; i &lt; len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = &amp;quot; &amp;quot;
    }
    fmt.Println(s)
}`, `68341238369279100000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Echo1 prints its command-line arguments.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; main

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;os&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sep &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        s &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; sep &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        sep &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;3272764814586049000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// Echo2 prints its command-line arguments.
package main

import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;os&amp;quot;
)

func main() {
    s, sep := &amp;quot;&amp;quot;, &amp;quot;&amp;quot;
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = &amp;quot; &amp;quot;
    }
    fmt.Println(s)
}`, `3272764814586049000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Echo2 prints its command-line arguments.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; main

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;os&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sep &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arg &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        s &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; sep &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; arg
        sep &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;80897944342115680000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`s := &amp;quot;&amp;quot; // 一条短变量声明，最简洁，但只能用在函数内部，而不能用于包变量
var s string // 依赖于字符串的默认初始化零值机制，被初始化为 &amp;quot;&amp;quot;
var s = &amp;quot;&amp;quot; // 用得很少，除非同时声明多个变量
var s string = &amp;quot;&amp;quot; // 显式地标明变量的类型，当变量类型与初值类型相同时，类型冗余，但如果两者类型不同，变量类型就必须了`, `80897944342115680000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;s &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 一条短变量声明，最简洁，但只能用在函数内部，而不能用于包变量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; s &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 依赖于字符串的默认初始化零值机制，被初始化为 &quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; s &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 用得很少，除非同时声明多个变量&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; s &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 显式地标明变量的类型，当变量类型与初值类型相同时，类型冗余，但如果两者类型不同，变量类型就必须了&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;实践中一般使用前两种形式中的某个，初始值重要的话就显式地指定变量的类型，否则使用隐式初始化。&lt;/p&gt;
&lt;p&gt;每次循环迭代字符串 s 的内容都会更新。+= 连接原字符串、空格和下个参数，产生新字符串，并把它赋值给 s。s 原来的内容已经不再使用，将在适当时机对它进行垃圾回收。&lt;/p&gt;
&lt;p&gt;如果连接涉及的数据量很大，这种方式代价高昂。一种简单且高效的解决方案是使用 strings 包的 Join 函数：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;94579031515114420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`func main() {
    fmt.Println(strings.Join(os.Args[1:], &amp;quot; &amp;quot;))
}`, `94579031515114420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后，如果不关心输出格式，只想看看输出值，或许只是为了调试，可以用 Println 为我们格式化输出。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29559370303979990000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`fmt.Println(os.Args[1:])`, `29559370303979990000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;go&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;go&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;go&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-go line-numbers&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这条语句的输出结果跟 strings.Join 得到的结果很像，只是被放到了一对方括号里。切片都会被打印成这种格式。&lt;/p&gt;
&lt;p&gt;// TODO &lt;a href=&quot;https://golang-china.github.io/gopl-zh/preface-zh.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://golang-china.github.io/gopl-zh/preface-zh.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[系统设计深入学习]]></title><description><![CDATA[高并发架构 消息队列 为什么使用消息队列？ 优点 解耦：通过一个 MQ，Pub/Sub 发布订阅消息这么一个模型，A 系统就跟其它系统彻底解耦了。 异步：任务发到消息队列，由消费者异步消费 削峰：任务发到消息队列，由消费者决定消费速度 缺点 系统可用性降低：MQ…]]></description><link>https://blog.towavephone.com/system-design-deep-learn/</link><guid isPermaLink="false">https://blog.towavephone.com/system-design-deep-learn/</guid><pubDate>Thu, 14 Sep 2023 13:47:01 GMT</pubDate><content:encoded>&lt;h1 id=&quot;高并发架构&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E5%B9%B6%E5%8F%91%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高并发架构&lt;/h1&gt;
&lt;h2 id=&quot;消息队列&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消息队列&lt;/h2&gt;
&lt;h3 id=&quot;为什么使用消息队列？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为什么使用消息队列？&lt;/h3&gt;
&lt;h4 id=&quot;优点&quot;&gt;&lt;a href=&quot;#%E4%BC%98%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;优点&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;解耦：通过一个 MQ，Pub/Sub 发布订阅消息这么一个模型，A 系统就跟其它系统彻底解耦了。&lt;/li&gt;
&lt;li&gt;异步：任务发到消息队列，由消费者异步消费&lt;/li&gt;
&lt;li&gt;削峰：任务发到消息队列，由消费者决定消费速度&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;缺点&quot;&gt;&lt;a href=&quot;#%E7%BC%BA%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缺点&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;系统可用性降低：MQ 挂了如何处理？即如何保证消息队列的高可用？&lt;/li&gt;
&lt;li&gt;系统复杂度提高：怎么保证消息没有重复消费？怎么处理消息丢失的情况？怎么保证消息传递的顺序性？&lt;/li&gt;
&lt;li&gt;一致性问题：A 系统处理完直接返回成功，发到消息队列后供 B、C、D 消费，如果 B、D 成功、C 失败怎么处理？&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;kafka、activemq、rabbitmq、rocketmq-有什么优缺点？&quot;&gt;&lt;a href=&quot;#kafka%E3%80%81activemq%E3%80%81rabbitmq%E3%80%81rocketmq-%E6%9C%89%E4%BB%80%E4%B9%88%E4%BC%98%E7%BC%BA%E7%82%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点？&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;ActiveMQ&lt;/th&gt;
&lt;th&gt;RabbitMQ&lt;/th&gt;
&lt;th&gt;RocketMQ&lt;/th&gt;
&lt;th&gt;Kafka&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;单机吞吐量&lt;/td&gt;
&lt;td&gt;万级，比 RocketMQ、Kafka 低一个数量级&lt;/td&gt;
&lt;td&gt;同 ActiveMQ&lt;/td&gt;
&lt;td&gt;10 万级，支撑高吞吐&lt;/td&gt;
&lt;td&gt;10 万级，高吞吐，一般配合大数据类的系统来进行实时数据计算、日志采集等场景&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;topic 数量对吞吐量的影响&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;topic 可以达到几百/几千的级别，吞吐量会有较小幅度的下降，这是 RocketMQ 的一大优势，在同等机器下，可以支撑大量的 topic&lt;/td&gt;
&lt;td&gt;topic 从几十到几百个时候，吞吐量会大幅度下降，在同等机器下，Kafka 尽量保证 topic 数量不要过多，如果要支撑大规模的 topic，需要增加更多的机器资源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;时效性&lt;/td&gt;
&lt;td&gt;ms 级&lt;/td&gt;
&lt;td&gt;微秒级，这是 RabbitMQ 的一大特点，延迟最低&lt;/td&gt;
&lt;td&gt;ms 级&lt;/td&gt;
&lt;td&gt;延迟在 ms 级以内&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可用性&lt;/td&gt;
&lt;td&gt;高，基于主从架构实现高可用&lt;/td&gt;
&lt;td&gt;同 ActiveMQ&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;经过参数优化配置，可以做到 0 丢失&lt;/td&gt;
&lt;td&gt;同 RocketMQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;功能支持&lt;/td&gt;
&lt;td&gt;MQ 领域的功能极其完备&lt;/td&gt;
&lt;td&gt;基于 erlang 开发，并发能力很强，性能极好，延时很低&lt;/td&gt;
&lt;td&gt;MQ 功能较为完善，还是分布式的，扩展性好&lt;/td&gt;
&lt;td&gt;功能较为简单，主要支持简单的 MQ 功能，在大数据领域的实时计算以及日志采集被大规模使用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;综上，各种对比之后，有如下建议：&lt;/p&gt;
&lt;p&gt;一般的业务系统要引入 MQ，最早大家都用 ActiveMQ，但是现在确实大家用的不多了，没经过大规模吞吐量场景的验证，社区也不是很活跃，所以大家还是算了吧，我个人不推荐用这个了。&lt;/p&gt;
&lt;p&gt;后来大家开始用 RabbitMQ，但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它，对公司而言，几乎处于不可控的状态，但是确实人家是开源的，比较稳定的支持，活跃度也高。&lt;/p&gt;
&lt;p&gt;不过现在确实越来越多的公司会去用 RocketMQ，确实很不错，毕竟是阿里出品，但社区可能有突然黄掉的风险（目前 RocketMQ 已捐给 &lt;a href=&quot;https://github.com/apache/rocketmq&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Apache&lt;/a&gt;，但 GitHub 上的活跃度其实不算高）对自己公司技术实力有绝对自信的，推荐用 RocketMQ，否则回去老老实实用 RabbitMQ 吧，人家有活跃的开源社区，绝对不会黄。&lt;/p&gt;
&lt;p&gt;所以&lt;strong&gt;中小型公司&lt;/strong&gt;，技术实力较为一般，技术挑战不是特别高，用 RabbitMQ 是不错的选择；&lt;strong&gt;大型公司&lt;/strong&gt;，基础架构研发实力较强，用 RocketMQ 是很好的选择。&lt;/p&gt;
&lt;p&gt;如果是&lt;strong&gt;大数据领域&lt;/strong&gt;的实时计算、日志采集等场景，用 Kafka 是业内标准的，绝对没问题，社区活跃度很高，绝对不会黄，何况几乎是全世界这个领域的事实性规范。&lt;/p&gt;
&lt;h3 id=&quot;如何保证消息队列的高可用？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证消息队列的高可用？&lt;/h3&gt;
&lt;h4 id=&quot;rabbitmq-的高可用性&quot;&gt;&lt;a href=&quot;#rabbitmq-%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ 的高可用性&lt;/h4&gt;
&lt;p&gt;RabbitMQ 是比较有代表性的，因为是基于主从（非分布式）做高可用性的，RabbitMQ 有三种模式：单机模式、普通集群模式、镜像集群模式。&lt;/p&gt;
&lt;h5 id=&quot;单机模式&quot;&gt;&lt;a href=&quot;#%E5%8D%95%E6%9C%BA%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;单机模式&lt;/h5&gt;
&lt;p&gt;单机模式，就是 Demo 级别的，一般就是你本地启动了玩玩儿的，没人生产用单机模式。&lt;/p&gt;
&lt;h5 id=&quot;普通集群模式（无高可用性）&quot;&gt;&lt;a href=&quot;#%E6%99%AE%E9%80%9A%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%EF%BC%88%E6%97%A0%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;普通集群模式（无高可用性）&lt;/h5&gt;
&lt;p&gt;普通集群模式，意思就是在多台机器上启动多个 RabbitMQ 实例，每台机器启动一个。你创建的 queue，只会放在一个 RabbitMQ 实例上，但是每个实例都同步 queue 的元数据（元数据可以认为是 queue 的一些配置信息，通过元数据，可以找到 queue 所在实例）。你消费的时候，实际上如果连接到了另外一个实例，那么那个实例会从 queue 所在实例上拉取数据过来。&lt;/p&gt;
&lt;p&gt;缺点如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没做到分布式，就是个普通的集群：因为这导致你要么消费者每次随机连接一个实例然后拉取数据，要么固定连接那个 queue 所在实例消费数据，前者有数据拉取的开销，后者导致单实例性能瓶颈。&lt;/li&gt;
&lt;li&gt;可用性无保障：如果那个放 queue 的实例宕机了，会导致接下来其他实例就无法从那个实例拉取，如果你开启了消息持久化，让 RabbitMQ 落地存储消息的话，消息不一定会丢，得等这个实例恢复了，然后才可以继续从这个 queue 拉取数据&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;唯一的好处就是提高在集群中多个节点服务某个 queue 的读写操作&lt;/p&gt;
&lt;h5 id=&quot;镜像集群模式（高可用性）&quot;&gt;&lt;a href=&quot;#%E9%95%9C%E5%83%8F%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%EF%BC%88%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;镜像集群模式（高可用性）&lt;/h5&gt;
&lt;p&gt;这种模式，才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是，在镜像集群模式下，你创建的 queue，无论是元数据还是 queue 里的消息都会存在于多个实例上，就是说，每个 RabbitMQ 节点都有这个 queue 的一个完整镜像，包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候，都会自动把消息同步到多个实例的 queue 上。&lt;/p&gt;
&lt;p&gt;开启这个模式的方法：管理控制台新增镜像集群模式的策略，指定的时候是可以要求数据同步到所有节点，也可以要求同步到指定数量的节点，再次创建 queue 的时候，应用这个策略，就会自动将数据同步到其他的节点上去了。&lt;/p&gt;
&lt;p&gt;优点如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;可用性相对较高：你任何一个机器宕机了，没事儿，其它机器（节点）还包含了这个 queue 的完整数据，别的 consumer 都可以到其它节点上去消费数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;缺点如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;性能开销大：消息需要同步到所有机器上，导致网络带宽压力和消耗很重&lt;/li&gt;
&lt;li&gt;非分布式的，就没有扩展性可言：如果某个 queue 负载很重，你加机器，新增的机器也包含了这个 queue 的所有数据，并没有办法线性扩展你的 queue。你想，如果这个 queue 的数据量很大，大到这个机器上的容量无法容纳了，此时该怎么办呢？&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;kafka-的高可用性&quot;&gt;&lt;a href=&quot;#kafka-%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka 的高可用性&lt;/h4&gt;
&lt;p&gt;基本架构：由多个 broker 组成，每个 broker 是一个节点；你创建一个 topic，这个 topic 可以划分为多个 partition，每个 partition 可以存在于不同的 broker 上，每个 partition 就放一部分数据。&lt;/p&gt;
&lt;p&gt;这就是天然的分布式消息队列，就是说一个 topic 的数据，是分散放在多个机器上的，每个机器就放一部分数据。&lt;/p&gt;
&lt;p&gt;实际上 RabbitMQ 之类的，并不是分布式消息队列，它就是传统的消息队列，只不过提供了一些集群、HA（High Availability, 高可用性） 的机制而已，因为无论怎么玩儿，RabbitMQ 一个 queue 的数据都是放在一个节点里的，镜像集群模式下，也是每个节点都放这个 queue 的完整数据。&lt;/p&gt;
&lt;p&gt;Kafka 0.8 以前，是没有 HA 机制的，就是任何一个 broker 宕机了，那个 broker 上的 partition 就废了，没法写也没法读，没有什么高可用性可言。&lt;/p&gt;
&lt;p&gt;比如说，我们假设创建了一个 topic，指定其 partition 数量是 3 个，分别在三台机器上。但是，如果第二台机器宕机了，会导致这个 topic 的 1/3 的数据就丢了，因此这个是做不到高可用的。&lt;/p&gt;
&lt;p&gt;Kafka 0.8 以后，提供了 HA 机制，就是 replica（复制品） 副本机制。每个 partition 的数据都会同步到其它机器上，形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来，那么生产和消费都跟这个 leader 打交道，然后其他 replica 就是 follower。写的时候，leader 会负责把数据同步到所有 follower 上去，读的时候就直接读 leader 上的数据即可。只能读写 leader？很简单，要是你可以随意读写每个 follower，那么就要关心数据一致性的问题，系统复杂度太高，很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上，这样才可以提高容错性。&lt;/p&gt;
&lt;p&gt;这么搞，就有所谓的高可用性了，因为如果某个 broker 宕机了，没事儿，那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader，那么此时会从 follower 中重新选举一个新的 leader 出来，大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。&lt;/p&gt;
&lt;p&gt;写数据的时候，生产者就写 leader，然后 leader 将数据落地写本地磁盘，接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了，就会发送 ack 给 leader，leader 收到所有 follower 的 ack 之后，就会返回写成功的消息给生产者。（当然，这只是其中一种模式，还可以适当调整这个行为）&lt;/p&gt;
&lt;p&gt;消费的时候，只会从 leader 去读，但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候，这个消息才会被消费者读到。&lt;/p&gt;
&lt;h3 id=&quot;如何保证消息不被重复消费？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E6%B6%88%E6%81%AF%E4%B8%8D%E8%A2%AB%E9%87%8D%E5%A4%8D%E6%B6%88%E8%B4%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证消息不被重复消费？&lt;/h3&gt;
&lt;h4 id=&quot;背景&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h4&gt;
&lt;p&gt;首先，比如 RabbitMQ、RocketMQ、Kafka，都有可能会出现消息重复消费的问题，正常。因为这问题通常不是 MQ 自己保证的，是由我们开发来保证的。挑一个 Kafka 来举个例子，说说怎么重复消费吧。&lt;/p&gt;
&lt;p&gt;Kafka 实际上有个 offset 的概念，就是每个消息写进去，都有一个 offset，代表消息的序号，然后 consumer 消费了数据之后，每隔一段时间（定时定期），会把自己消费过的消息的 offset 提交一下，表示“我已经消费过了，下次我要是重启啥的，你就让我继续从上次消费到的 offset 来继续消费吧”。&lt;/p&gt;
&lt;p&gt;但是凡事总有意外，比如我们之前生产经常遇到的，就是你有时候重启系统，看你怎么重启了，如果碰到点着急的，直接 kill 进程了，再重启。这会导致 consumer 有些消息处理了，但是没来得及提交 offset，尴尬了。重启之后，少数消息会再次消费一次。&lt;/p&gt;
&lt;p&gt;有这么个场景。数据 1/2/3 依次进入 Kafka，Kafka 会给这三条数据每条分配一个 offset，代表这条数据的序号，我们就假设分配的 offset 依次是 152/153/154。消费者从 Kafka 去消费的时候，也是按照这个顺序去消费。假如当消费者消费了 offset=153 的这条数据，刚准备去提交 offset 到 Zookeeper，此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交，Kafka 也就不知道你已经消费了 offset=153 这条数据。那么重启之后，消费者会找 Kafka 说，嘿，哥儿们，你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功，那么数据 1/2 会再次传过来，如果此时消费者没有去重的话，那么就会导致重复消费。&lt;/p&gt;
&lt;p&gt;如果消费者干的事儿是拿一条数据就往数据库里写一条，会导致说，你可能就把数据 1/2 在数据库里插入了 2 次，那么数据就错啦。&lt;/p&gt;
&lt;h4 id=&quot;解决方案&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解决方案&lt;/h4&gt;
&lt;p&gt;需要保证重复消费的幂等性，需要结合具体场景&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;比如你拿个数据要写库，你先根据主键查一下，如果这数据都有了，你就别插入了，update 一下好吧。&lt;/li&gt;
&lt;li&gt;比如你是写 Redis，那没问题了，反正每次都是 set，天然幂等性。&lt;/li&gt;
&lt;li&gt;比如你不是上面两个场景，那做的稍微复杂一点，你需要让生产者发送每条数据的时候，里面加一个全局唯一的 id，类似订单 id 之类的东西，然后你这里消费到了之后，先根据这个 id 去比如 Redis 里查一下，之前消费过吗？如果没有消费过，你就处理，然后这个 id 写 Redis。如果消费过了，那你就别处理了，保证别重复处理相同的消息即可。&lt;/li&gt;
&lt;li&gt;比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了，重复数据插入只会报错，不会导致数据库中出现脏数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;如何保证消息的可靠性传输？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E6%B6%88%E6%81%AF%E7%9A%84%E5%8F%AF%E9%9D%A0%E6%80%A7%E4%BC%A0%E8%BE%93%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证消息的可靠性传输？&lt;/h3&gt;
&lt;p&gt;数据的丢失问题，可能出现在生产者、MQ、消费者中，咱们从 RabbitMQ 和 Kafka 分别来分析一下吧。&lt;/p&gt;
&lt;h4 id=&quot;rabbitmq&quot;&gt;&lt;a href=&quot;#rabbitmq&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ&lt;/h4&gt;
&lt;h5 id=&quot;生产者弄丢了数据&quot;&gt;&lt;a href=&quot;#%E7%94%9F%E4%BA%A7%E8%80%85%E5%BC%84%E4%B8%A2%E4%BA%86%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;生产者弄丢了数据&lt;/h5&gt;
&lt;p&gt;生产者将数据发送到 RabbitMQ 的时候，可能数据就在半路给搞丢了，因为网络问题啥的，都有可能。&lt;/p&gt;
&lt;p&gt;此时可以选择用 RabbitMQ 提供的事务功能，就是生产者发送数据之前开启 RabbitMQ 事务 &lt;code class=&quot;language-text&quot;&gt;channel.txSelect()&lt;/code&gt;，然后发送消息，如果消息没有成功被 RabbitMQ 接收到，那么生产者会收到异常报错，此时就可以回滚事务 &lt;code class=&quot;language-text&quot;&gt;channel.txRollback()&lt;/code&gt;，然后重试发送消息；如果收到了消息，那么可以提交事务 channel.txCommit() 。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;49536369317830074000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`try {
    // 通过工厂创建连接
    connection = factory.newConnection();
    // 获取通道
    channel = connection.createChannel();
    // 开启事务
    channel.txSelect();

    // 这里发送消息
    channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());

    // 模拟出现异常
    int result = 1 / 0;

    // 提交事务
    channel.txCommit();
} catch (IOException | TimeoutException e) {
    // 捕捉异常，回滚事务
    channel.txRollback();
}`, `49536369317830074000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 通过工厂创建连接&lt;/span&gt;
    connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取通道&lt;/span&gt;
    channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 开启事务&lt;/span&gt;
    channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;txSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 这里发送消息&lt;/span&gt;
    channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basicPublish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exchange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PERSISTENT_TEXT_PLAIN&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; msg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 模拟出现异常&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 提交事务&lt;/span&gt;
    channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;txCommit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeoutException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 捕捉异常，回滚事务&lt;/span&gt;
    channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;txRollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是问题是，RabbitMQ 事务机制（同步）一搞，基本上吞吐量会下来，因为太耗性能。&lt;/p&gt;
&lt;p&gt;所以一般来说，如果你要确保说写 RabbitMQ 的消息别丢，可以开启 confirm 模式，在生产者那里设置开启 confirm 模式之后，你每次写的消息都会分配一个唯一的 id，然后如果写入了 RabbitMQ 中，RabbitMQ 会给你回传一个 ack 消息，告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息，会回调你的一个 nack 接口，告诉你这个消息接收失败，你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态，如果超过一定时间还没接收到这个消息的回调，那么你可以重发。&lt;/p&gt;
&lt;p&gt;事务机制和 confirm 机制最大的不同在于，事务机制是同步的，你提交一个事务之后会阻塞在那儿，但是 confirm 机制是异步的，你发送个消息之后就可以发送下一个消息，然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。&lt;/p&gt;
&lt;p&gt;所以一般在生产者这块避免数据丢失，都是用 confirm 机制的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;已经在 transaction 事务模式的 channel 是不能再设置成 confirm 模式的，即这两种模式是不能共存的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;客户端实现生产者 confirm 有 3 种方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;普通 confirm 模式：每发送一条消息后，调用 waitForConfirms() 方法，等待服务器端 confirm，如果服务端返回 false 或者在一段时间内都没返回，客户端可以进行消息重发。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54996012808372610000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
if (!channel.waitForConfirms()) {
   // 消息发送失败
   // ...
}`, `54996012808372610000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basicPublish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PERSISTENT_TEXT_PLAIN&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msg_10B&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitForConfirms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 消息发送失败&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;批量 confirm 模式：每发送一批消息后，调用 waitForConfirms() 方法，等待服务端 confirm。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74979093259983500000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`channel.confirmSelect();
for (int i = 0; i &lt; batchCount; ++i) {
   channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
}
if (!channel.waitForConfirms()) {
   // 消息发送失败
   // ...
}`, `74979093259983500000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;confirmSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; batchCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basicPublish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PERSISTENT_TEXT_PLAIN&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msg_10B&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitForConfirms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// 消息发送失败&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异步 confirm 模式：提供一个回调方法，服务端 confirm 了一条或者多条消息后客户端会回调这个方法。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;84309749334475880000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`SortedSet&lt;Long&gt; confirmSet = Collections.synchronizedSortedSet(new TreeSet&lt;Long&gt;());
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
   public void handleAck(long deliveryTag, boolean multiple) throws IOException {
       if (multiple) {
           confirmSet.headSet(deliveryTag + 1).clear();
       } else {
           confirmSet.remove(deliveryTag);
       }
   }

   public void handleNack(long deliveryTag, boolean multiple) throws IOException {
       System.out.println(&amp;quot;Nack, SeqNo: &amp;quot; + deliveryTag + &amp;quot;, multiple: &amp;quot; + multiple);
       if (multiple) {
           confirmSet.headSet(deliveryTag + 1).clear();
       } else {
           confirmSet.remove(deliveryTag);
       }
   }
});

while (true) {
   long nextSeqNo = channel.getNextPublishSeqNo();
   channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
   confirmSet.add(nextSeqNo);
}`, `84309749334475880000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;SortedSet&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; confirmSet &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;synchronizedSortedSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TreeSet&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;confirmSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addConfirmListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleAck&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; deliveryTag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           confirmSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;headSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deliveryTag &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           confirmSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deliveryTag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

   &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleNack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; deliveryTag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Nack, SeqNo: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; deliveryTag &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;, multiple: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           confirmSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;headSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deliveryTag &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
           confirmSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deliveryTag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; nextSeqNo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getNextPublishSeqNo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basicPublish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exchangeName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PERSISTENT_TEXT_PLAIN&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msg_10B&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   confirmSet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextSeqNo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;rabbitmq-弄丢了数据&quot;&gt;&lt;a href=&quot;#rabbitmq-%E5%BC%84%E4%B8%A2%E4%BA%86%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ 弄丢了数据&lt;/h5&gt;
&lt;p&gt;就是 RabbitMQ 自己弄丢了数据，这个你必须开启 RabbitMQ 的持久化，就是消息写入之后会持久化到磁盘，哪怕是 RabbitMQ 自己挂了，恢复之后会自动读取之前存储的数据，一般数据不会丢。除非极其罕见的是，RabbitMQ 还没持久化，自己就挂了，可能导致少量数据丢失，但是这个概率较小。&lt;/p&gt;
&lt;p&gt;设置持久化有两个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据，但是它是不会持久化 queue 里的数据的。&lt;/li&gt;
&lt;li&gt;发送消息的时候将消息的 deliveryMode 设置为 2。就是将消息设置为持久化的，此时 RabbitMQ 就会将消息持久化到磁盘上去。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;必须要同时设置这两个持久化才行，RabbitMQ 哪怕是挂了，再次重启，也会从磁盘上重启恢复 queue，恢复这个 queue 里的数据。&lt;/p&gt;
&lt;p&gt;注意，哪怕是你给 RabbitMQ 开启了持久化机制，也有一种可能，就是这个消息写到了 RabbitMQ 中，但是还没来得及持久化到磁盘上，结果不巧，此时 RabbitMQ 挂了，就会导致内存里的一点点数据丢失。&lt;/p&gt;
&lt;p&gt;所以，持久化可以跟生产者那边的 confirm 机制配合起来，只有消息被持久化到磁盘之后，才会通知生产者 ack 了，所以哪怕是在持久化到磁盘之前，RabbitMQ 挂了，数据丢了，生产者收不到 ack ，你也是可以自己重发的。&lt;/p&gt;
&lt;h5 id=&quot;消费端弄丢了数据&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E8%B4%B9%E7%AB%AF%E5%BC%84%E4%B8%A2%E4%BA%86%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消费端弄丢了数据&lt;/h5&gt;
&lt;p&gt;RabbitMQ 如果丢失了数据，主要是因为你消费的时候，刚消费到，还没处理，结果进程挂了，比如重启了，那么就尴尬了，RabbitMQ 认为你都消费了，这数据就丢了。&lt;/p&gt;
&lt;p&gt;这个时候得用 RabbitMQ 提供的 ack 机制，简单来说，就是你必须关闭 RabbitMQ 的自动 ack，可以通过一个 api 来调用就行，然后每次你自己代码里确保处理完的时候，再在程序里 ack 一把。这样的话，如果你还没处理完，不就没有 ack 了？那 RabbitMQ 就认为你还没处理完，这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理，消息是不会丢的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为了保证消息从队列中可靠地到达消费者，RabbitMQ 提供了消息确认机制。消费者在声明队列时，可以指定 noAck 参数，当 noAck=false，RabbitMQ 会等待消费者显式发回 ack 信号后，才从内存（和磁盘，如果是持久化消息）中移去消息。否则，一旦消息被消费者消费，RabbitMQ 会在队列中立即删除它。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;kafka&quot;&gt;&lt;a href=&quot;#kafka&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka&lt;/h4&gt;
&lt;h5 id=&quot;消费端弄丢了数据-1&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E8%B4%B9%E7%AB%AF%E5%BC%84%E4%B8%A2%E4%BA%86%E6%95%B0%E6%8D%AE-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消费端弄丢了数据&lt;/h5&gt;
&lt;p&gt;唯一可能导致消费者弄丢数据的情况，就是说，你消费到了这个消息，然后消费者那边自动提交了 offset，让 Kafka 以为你已经消费好了这个消息，但其实你才刚准备处理这个消息，你还没处理，你自己就挂了，此时这条消息就丢咯。&lt;/p&gt;
&lt;p&gt;这不是跟 RabbitMQ 差不多吗，大家都知道 Kafka 会自动提交 offset，那么只要关闭自动提交 offset，在处理完之后自己手动提交 offset，就可以保证数据不会丢。但是此时确实还是可能会有重复消费，比如你刚处理完，还没提交 offset，结果自己挂了，此时肯定会重复消费一次，自己保证幂等性就好了。&lt;/p&gt;
&lt;p&gt;生产环境碰到的一个问题，就是说我们的 Kafka 消费者消费到了数据之后是写到一个内存的 queue 里先缓冲一下，结果有的时候，你刚把消息写入内存 queue，然后消费者会自动提交 offset。然后此时我们重启了系统，就会导致内存 queue 里还没来得及处理的数据就丢失了。&lt;/p&gt;
&lt;h5 id=&quot;kafka-弄丢了数据&quot;&gt;&lt;a href=&quot;#kafka-%E5%BC%84%E4%B8%A2%E4%BA%86%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka 弄丢了数据&lt;/h5&gt;
&lt;p&gt;这块比较常见的一个场景，就是 Kafka 某个 broker 宕机，然后重新选举 partition 的 leader。大家想想，要是此时其他的 follower 刚好还有些数据没有同步，结果此时 leader 挂了，然后选举某个 follower 成 leader 之后，不就少了一些数据？这就丢了一些数据啊。&lt;/p&gt;
&lt;p&gt;生产环境也遇到过，之前 Kafka 的 leader 机器宕机了，将 follower 切换为 leader 之后，就会发现说这个数据就丢了。&lt;/p&gt;
&lt;p&gt;所以此时一般是要求起码设置如下 4 个参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给 topic 设置 replication.factor 参数：这个值必须大于 1，要求每个 partition 必须有至少 2 个副本。&lt;/li&gt;
&lt;li&gt;在 Kafka 服务端设置 min.insync.replicas 参数：这个值必须大于 1，这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系，没掉队，这样才能确保 leader 挂了还有一个 follower 吧。&lt;/li&gt;
&lt;li&gt;在 producer 端设置 acks=all：这个是要求每条数据，必须是写入所有 replica 之后，才能认为是写成功了。&lt;/li&gt;
&lt;li&gt;在 producer 端设置 retries=MAX（很大很大很大的一个值，无限次重试的意思）：这个是要求一旦写入失败，就无限重试，卡在这里了。我们生产环境就是按照上述要求配置的，这样配置之后，至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障，进行 leader 切换时，数据不会丢失。&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;生产者会不会弄丢数据？&quot;&gt;&lt;a href=&quot;#%E7%94%9F%E4%BA%A7%E8%80%85%E4%BC%9A%E4%B8%8D%E4%BC%9A%E5%BC%84%E4%B8%A2%E6%95%B0%E6%8D%AE%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;生产者会不会弄丢数据？&lt;/h5&gt;
&lt;p&gt;如果按照上述的思路设置了 acks=all，一定不会丢，要求是你的 leader 接收到消息，所有的 follower 都同步到了消息之后，才认为本次写成功了。如果没满足这个条件，生产者会自动不断的重试，重试无限次。&lt;/p&gt;
&lt;h4 id=&quot;rocketmq&quot;&gt;&lt;a href=&quot;#rocketmq&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RocketMQ&lt;/h4&gt;
&lt;h5 id=&quot;消息丢失的场景&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E7%9A%84%E5%9C%BA%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消息丢失的场景&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;生产者发送消息到 MQ 有可能丢失消息&lt;/li&gt;
&lt;li&gt;MQ 收到消息后写入硬盘可能丢失消息&lt;/li&gt;
&lt;li&gt;消息写入硬盘后，硬盘坏了丢失消息&lt;/li&gt;
&lt;li&gt;消费者消费 MQ 也可能丢失消息&lt;/li&gt;
&lt;li&gt;整个 MQ 节点挂了丢失消息&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;生产者发送消息时如何保证不丢失？&quot;&gt;&lt;a href=&quot;#%E7%94%9F%E4%BA%A7%E8%80%85%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%97%B6%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%B8%8D%E4%B8%A2%E5%A4%B1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;生产者发送消息时如何保证不丢失？&lt;/h5&gt;
&lt;p&gt;解决发送时消息丢失的问题可以采用 RocketMQ 自带的事物消息机制&lt;/p&gt;
&lt;p&gt;事物消息原理：首先生产者会发送一个 half 消息(对原始消息的封装)，该消息对消费者不可见，MQ 通过 ACK 机制返回消息接受状态，生产者执行本地事务并且返回给 MQ 一个状态(Commit、RollBack 等)，如果是 Commit 的话 MQ 就会把消息给到下游，RollBack 的话就会丢弃该消息，状态如果为 UnKnow 的话会过一段时间回查本地事务状态，默认回查 15 次，一直是 UnKnow 状态的话就会丢弃此消息。&lt;/p&gt;
&lt;p&gt;为什么先发一个 half 消息，作用就是先判断下 MQ 有没有问题，服务正不正常。&lt;/p&gt;
&lt;h5 id=&quot;mq-收到消息后写入硬盘如何保证不丢失？&quot;&gt;&lt;a href=&quot;#mq-%E6%94%B6%E5%88%B0%E6%B6%88%E6%81%AF%E5%90%8E%E5%86%99%E5%85%A5%E7%A1%AC%E7%9B%98%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%B8%8D%E4%B8%A2%E5%A4%B1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MQ 收到消息后写入硬盘如何保证不丢失？&lt;/h5&gt;
&lt;p&gt;数据存盘绕过缓存，改为同步刷盘，这一步需要修改 Broker 的配置文件，将 flushDiskType 改为 &lt;code class=&quot;language-text&quot;&gt;SYNC_FLUSH&lt;/code&gt; 同步刷盘策略，默认的是 &lt;code class=&quot;language-text&quot;&gt;ASYNC_FLUSH&lt;/code&gt; 异步刷盘，一旦同步刷盘返回成功，那么就一定保证消息已经持久化到磁盘中了。&lt;/p&gt;
&lt;h5 id=&quot;消息写入硬盘后，硬盘坏了如何保证不丢失？&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E6%81%AF%E5%86%99%E5%85%A5%E7%A1%AC%E7%9B%98%E5%90%8E%EF%BC%8C%E7%A1%AC%E7%9B%98%E5%9D%8F%E4%BA%86%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%B8%8D%E4%B8%A2%E5%A4%B1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消息写入硬盘后，硬盘坏了如何保证不丢失？&lt;/h5&gt;
&lt;p&gt;为了保证磁盘损坏导致丢失数据，RocketMQ 采用主从机构，集群部署，Leader 中的数据在多个 Follower 中都存有备份，防止单点故障导致数据丢失。&lt;/p&gt;
&lt;p&gt;Master 节点挂了怎么办？Master 节点挂了之后 DLedger 登场&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接管 MQ 的 commitLog&lt;/li&gt;
&lt;li&gt;选举从节点&lt;/li&gt;
&lt;li&gt;文件复制 uncommited 状态，多半从节点收到之后改为 commited&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;消费者消费-mq-如何保证不丢失？&quot;&gt;&lt;a href=&quot;#%E6%B6%88%E8%B4%B9%E8%80%85%E6%B6%88%E8%B4%B9-mq-%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%B8%8D%E4%B8%A2%E5%A4%B1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;消费者消费 MQ 如何保证不丢失？&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;如果是网络问题导致的消费失败可以进行重试机制，默认每条消息重试 16 次&lt;/li&gt;
&lt;li&gt;多线程异步消费失败，MQ 认为已经消费成功但是实际上对于业务逻辑来说消息是没有落地的，解决方案就是按照 mq 官方推荐的先执行本地事务再返回成功状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;整个-mq-节点挂了如何保证不丢失？&quot;&gt;&lt;a href=&quot;#%E6%95%B4%E4%B8%AA-mq-%E8%8A%82%E7%82%B9%E6%8C%82%E4%BA%86%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E4%B8%8D%E4%B8%A2%E5%A4%B1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;整个 MQ 节点挂了如何保证不丢失？&lt;/h5&gt;
&lt;p&gt;这种极端情况可以消息发送失败之后先存入本地，例如放到缓存中，另外启动一个线程扫描缓存的消息去重试发送。&lt;/p&gt;
&lt;h3 id=&quot;如何保证消息的顺序性？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E6%B6%88%E6%81%AF%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%80%A7%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证消息的顺序性？&lt;/h3&gt;
&lt;h4 id=&quot;背景-1&quot;&gt;&lt;a href=&quot;#%E8%83%8C%E6%99%AF-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;背景&lt;/h4&gt;
&lt;p&gt;以前做过一个 mysql binlog 同步的系统，压力还是非常大的，日同步数据要达到上亿，就是说数据从一个 mysql 库原封不动地同步到另一个 mysql 库里面去（mysql -&gt; mysql）。常见的一点在于说比如大数据 team，就需要同步一个 mysql 库过来，对公司的业务系统的数据做各种复杂的操作。&lt;/p&gt;
&lt;p&gt;你在 mysql 里增删改一条数据，对应出来了增删改 3 条 binlog 日志，接着这三条 binlog 发送到 MQ 里面，再消费出来依次执行，起码得保证人家是按照顺序来的吧？不然本来是：增加、修改、删除；你愣是换了顺序给执行成删除、修改、增加，不全错了么。&lt;/p&gt;
&lt;p&gt;本来这个数据同步过来，应该最后这个数据被删除了；结果你搞错了这个顺序，最后这个数据保留下来了，数据同步就出错了。&lt;/p&gt;
&lt;p&gt;先看看顺序会错乱的俩场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RabbitMQ：一个 queue，多个 consumer。比如，生产者向 RabbitMQ 里发送了三条数据，顺序依次是 data1/data2/data3，压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条，结果消费者 2 先执行完操作，把 data2 存入数据库，然后是 data1/data3。这不明显乱了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kafka：比如说我们建了一个 topic，有三个 partition。生产者在写的时候，其实可以指定一个 key，比如说我们指定了某个订单 id 作为 key，那么这个订单相关的数据，一定会被分发到同一个 partition 中去，而且这个 partition 中的数据一定是有顺序的。&lt;/p&gt;
&lt;p&gt;消费者从 partition 中取出来数据的时候，也一定是有顺序的。到这里，顺序还是 ok 的，没有错乱。接着，我们在消费者里可能会搞多个线程来并发处理消息。因为如果消费者是单线程消费处理，而处理比较耗时的话，比如处理一条消息耗时几十 ms，那么 1 秒钟只能处理几十条消息，这吞吐量太低了。而多个线程并发跑的话，顺序可能就乱掉了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;解决方案-1&quot;&gt;&lt;a href=&quot;#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;解决方案&lt;/h4&gt;
&lt;h5 id=&quot;rabbitmq-1&quot;&gt;&lt;a href=&quot;#rabbitmq-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ&lt;/h5&gt;
&lt;p&gt;拆分多个 queue，每个 queue 一个 consumer，就是多一些 queue 而已，确实是麻烦点，这样也会造成吞吐量下降，可以在消费者内部采用多线程的方式去消费。&lt;/p&gt;
&lt;p&gt;或者就一个 queue 但是对应一个 consumer，然后这个 consumer 内部用内存队列做排队，然后分发给底层不同的 worker 来处理。&lt;/p&gt;
&lt;p&gt;注意，这里消费者不直接消费消息，而是将消息根据关键值（比如：订单 id）进行哈希，哈希值相同的消息保存到相同的内存队列里。也就是说，需要保证顺序的消息存到了相同的内存队列，然后由一个唯一的 worker 去处理。&lt;/p&gt;
&lt;p&gt;一句话概括就是同一个 id 下的操作放到同一个 queue 中，那么消费者消费的顺序就得到了保证，下面的 Kafka 同理&lt;/p&gt;
&lt;h5 id=&quot;kafka-1&quot;&gt;&lt;a href=&quot;#kafka-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;一个 topic，一个 partition，一个 consumer，内部单线程消费，单线程吞吐量太低，一般不会用这个。&lt;/li&gt;
&lt;li&gt;写 N 个内存 queue，具有相同 key 的数据都到同一个内存 queue；然后对于 N 个线程，每个线程分别消费一个内存 queue 即可，这样就能保证顺序性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;如何解决消息队列的延时以及过期失效问题？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E7%9A%84%E5%BB%B6%E6%97%B6%E4%BB%A5%E5%8F%8A%E8%BF%87%E6%9C%9F%E5%A4%B1%E6%95%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何解决消息队列的延时以及过期失效问题？&lt;/h3&gt;
&lt;h4 id=&quot;大量消息在-mq-里积压了几个小时了还没解决&quot;&gt;&lt;a href=&quot;#%E5%A4%A7%E9%87%8F%E6%B6%88%E6%81%AF%E5%9C%A8-mq-%E9%87%8C%E7%A7%AF%E5%8E%8B%E4%BA%86%E5%87%A0%E4%B8%AA%E5%B0%8F%E6%97%B6%E4%BA%86%E8%BF%98%E6%B2%A1%E8%A7%A3%E5%86%B3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;大量消息在 mq 里积压了几个小时了还没解决&lt;/h4&gt;
&lt;p&gt;几千万条数据在 MQ 里积压了七八个小时，从下午 4 点多，积压到了晚上 11 点多。这个是我们真实遇到过的一个场景，确实是线上故障了，这个时候要不然就是修复 consumer 的问题，让它恢复消费速度，然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。&lt;/p&gt;
&lt;p&gt;一个消费者一秒是 1000 条，一秒 3 个消费者是 3000 条，一分钟就是 18 万条。所以如果你积压了几百万到上千万的数据，即使消费者恢复了，也需要大概 1 小时的时间才能恢复过来。&lt;/p&gt;
&lt;p&gt;一般这个时候，只能临时紧急扩容了，具体操作步骤和思路如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先修复 consumer 的问题，确保其恢复消费速度，然后将现有 consumer 都停掉。&lt;/li&gt;
&lt;li&gt;新建一个 topic，partition 是原来的 10 倍，临时建立好原先 10 倍的 queue 数量。&lt;/li&gt;
&lt;li&gt;然后写一个临时的分发数据的 consumer 程序，这个程序部署上去消费积压的数据，消费之后不做耗时的处理，直接均匀轮询写入临时建立好的 10 倍数量的 queue。&lt;/li&gt;
&lt;li&gt;接着临时征用 10 倍的机器来部署 consumer，每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍，以正常的 10 倍速度来消费数据。&lt;/li&gt;
&lt;li&gt;等快速消费完积压数据之后，得恢复原先部署的架构，重新用原先的 consumer 机器来消费消息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;mq-中的消息过期失效了&quot;&gt;&lt;a href=&quot;#mq-%E4%B8%AD%E7%9A%84%E6%B6%88%E6%81%AF%E8%BF%87%E6%9C%9F%E5%A4%B1%E6%95%88%E4%BA%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;mq 中的消息过期失效了&lt;/h4&gt;
&lt;p&gt;假设你用的是 RabbitMQ，RabbtiMQ 是可以设置过期时间的，也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉，这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里，而是大量的数据会直接搞丢。&lt;/p&gt;
&lt;p&gt;这个情况下，就不是说要增加 consumer 消费积压的消息，因为实际上没啥积压，而是丢了大量的消息。我们可以采取一个方案，就是批量重导，这个我们之前线上也有类似的场景干过。就是大量积压的时候，我们当时就直接丢弃数据了，然后等过了高峰期以后，比如大家一起喝咖啡熬夜到晚上 12 点以后，用户都睡觉了。这个时候我们就开始写程序，将丢失的那批数据，写个临时程序，一点一点的查出来，然后重新灌入 mq 里面去，把白天丢的数据给他补回来。也只能是这样了。&lt;/p&gt;
&lt;p&gt;假设 1 万个订单积压在 mq 里面，没有处理，其中 1000 个订单都丢了，你只能手动写程序把那 1000 个订单给查出来，手动发到 mq 里去再补一次。&lt;/p&gt;
&lt;h4 id=&quot;mq-都快写满了&quot;&gt;&lt;a href=&quot;#mq-%E9%83%BD%E5%BF%AB%E5%86%99%E6%BB%A1%E4%BA%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;mq 都快写满了&lt;/h4&gt;
&lt;p&gt;如果消息积压在 mq 里，你很长时间都没有处理掉，此时导致 mq 都快写满了，咋办？这个还有别的办法吗？没有，谁让你第一个方案执行的太慢了，你临时写程序，接入数据来消费，消费一个丢弃一个，都不要了，快速消费掉所有的消息。然后走第二个方案，到了晚上再补数据吧。&lt;/p&gt;
&lt;h4 id=&quot;rocketmq-的处理方式&quot;&gt;&lt;a href=&quot;#rocketmq-%E7%9A%84%E5%A4%84%E7%90%86%E6%96%B9%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RocketMQ 的处理方式&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;提高消费并行度&lt;/p&gt;
&lt;p&gt;绝大部分消息消费行为都属于 IO 密集型，即可能是操作数据库，或者调用 RPC，这类消费行为的消费速度在于后端数据库或者外系统的吞吐量，通过增加消费并行度，可以提高总的消费吞吐量，但是并行度增加到一定程度，反而会下降。所以，应用必须要设置合理的并行度。如下有几种修改消费并行度的方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同一个 ConsumerGroup 下，通过增加 Consumer 实例数量来提高并行度（需要注意的是超过订阅队列数的 Consumer 实例无效）。&lt;/li&gt;
&lt;li&gt;可以通过加机器，或者在已有机器启动多个进程的方式。&lt;/li&gt;
&lt;li&gt;提高单个 Consumer 的消费并行线程，通过修改参数 consumeThreadMin、consumeThreadMax 实现。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;批量方式消费&lt;/p&gt;
&lt;p&gt;某些业务流程如果支持批量方式消费，则可以很大程度上提高消费吞吐量，例如订单扣款类应用，一次处理一个订单耗时 1s，一次处理 10 个订单可能也只耗时 2s，这样即可大幅度提高消费的吞吐量，通过设置 consumer 的 consumeMessageBatchMaxSize 参数，默认是 1，即一次只消费一条消息，例如设置为 N，那么每次消费的消息数小于等于 N。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;跳过非重要消息&lt;/p&gt;
&lt;p&gt;发生消息堆积时，如果消费速度一直追不上发送速度，如果业务对数据要求不高的话，可以选择丢弃不重要的消息。例如，当某个队列的消息数堆积到 100000 条以上，则尝试丢弃部分或全部消息，这样就可以快速追上发送消息的速度。示例代码如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;4052839338344083000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public ConsumeConcurrentlyStatus consumeMessage(
           List&lt;MessageExt&gt; msgs,
           ConsumeConcurrentlyContext context) {
  long offset = msgs.get(0).getQueueOffset();
  String maxOffset =
           msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);
  long diff = Long.parseLong(maxOffset) - offset;
  if (diff &gt; 100000) {
     // 消息堆积情况的特殊处理
     return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  }
  // 正常消费过程
  return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}`, `4052839338344083000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumeConcurrentlyStatus&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;consumeMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
           &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageExt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; msgs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token class-name&quot;&gt;ConsumeConcurrentlyContext&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; offset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; msgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getQueueOffset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; maxOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
           msgs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROPERTY_MAX_OFFSET&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; diff &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maxOffset&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; offset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;diff &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// 消息堆积情况的特殊处理&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumeConcurrentlyStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CONSUME_SUCCESS&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 正常消费过程&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumeConcurrentlyStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CONSUME_SUCCESS&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优化每条消息消费过程&lt;/p&gt;
&lt;p&gt;举例如下，某条消息的消费过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据消息从 DB 查询【数据 1】&lt;/li&gt;
&lt;li&gt;根据消息从 DB 查询【数据 2】&lt;/li&gt;
&lt;li&gt;复杂的业务计算&lt;/li&gt;
&lt;li&gt;向 DB 插入【数据 3】&lt;/li&gt;
&lt;li&gt;向 DB 插入【数据 4】&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这条消息的消费过程中有 4 次与 DB 的交互，如果按照每次 5ms 计算，那么总共耗时 20ms，假设业务计算耗时 5ms，那么总过耗时 25ms，所以如果能把 4 次 DB 交互优化为 2 次，那么总耗时就可以优化到 15ms，即总体性能提高了 40%。所以应用如果对延时敏感的话，可以把 DB 部署在 SSD 硬盘，相比于 SCSI 磁盘，前者的 RT 会小很多。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;如何设计一个消息队列？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何设计一个消息队列？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先这个 mq 得支持可伸缩性吧，就是需要的时候快速扩容，就可以增加吞吐量和容量，那怎么搞？设计个分布式的系统呗，参照一下 kafka 的设计理念，broker -&gt; topic -&gt; partition，每个 partition 放一个机器，就存一部分数据。如果现在资源不够了，简单啊，给 topic 增加 partition，然后做数据迁移，增加机器，不就可以存放更多数据，提供更高的吞吐量了？&lt;/li&gt;
&lt;li&gt;其次你得考虑一下这个 mq 的数据要不要落地磁盘吧？那肯定要了，落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊？顺序写，这样就没有磁盘随机读写的寻址开销，磁盘顺序读写的性能是很高的，这就是 kafka 的思路。&lt;/li&gt;
&lt;li&gt;其次你考虑一下你的 mq 的可用性啊？这个事儿，具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -&gt; leader &amp;#x26; follower -&gt; broker 挂了重新选举 leader 即可对外服务。&lt;/li&gt;
&lt;li&gt;能不能支持数据 0 丢失啊？可以的，参考我们之前说的那个 kafka 数据零丢失方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;搜索引擎&quot;&gt;&lt;a href=&quot;#%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;搜索引擎&lt;/h2&gt;
&lt;h3 id=&quot;es-是什么？&quot;&gt;&lt;a href=&quot;#es-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 是什么？&lt;/h3&gt;
&lt;h4 id=&quot;lucene-和-es-的前世今生&quot;&gt;&lt;a href=&quot;#lucene-%E5%92%8C-es-%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Lucene 和 ES 的前世今生&lt;/h4&gt;
&lt;p&gt;Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发，非常复杂，即便写一些简单的功能，也要写大量的 Java 代码，需要深入理解原理。&lt;/p&gt;
&lt;p&gt;ElasticSearch 基于 Lucene，隐藏了 lucene 的复杂性，提供了简单易用的 RESTful api / Java api 接口（另外还有其他语言的 api 接口）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分布式的文档存储引擎&lt;/li&gt;
&lt;li&gt;分布式的搜索引擎和分析引擎&lt;/li&gt;
&lt;li&gt;分布式，支持 PB 级数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;es-的核心概念&quot;&gt;&lt;a href=&quot;#es-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 的核心概念&lt;/h4&gt;
&lt;h5 id=&quot;near-realtime&quot;&gt;&lt;a href=&quot;#near-realtime&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Near Realtime&lt;/h5&gt;
&lt;p&gt;近实时，有两层意思：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从写入数据到数据可以被搜索到有一个小延迟（大概是 1s）&lt;/li&gt;
&lt;li&gt;基于 ES 执行搜索和分析可以达到秒级&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;cluster-集群&quot;&gt;&lt;a href=&quot;#cluster-%E9%9B%86%E7%BE%A4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cluster 集群&lt;/h5&gt;
&lt;p&gt;集群包含多个节点，每个节点属于哪个集群都是通过一个配置来决定的，对于中小型应用来说，刚开始一个集群就一个节点很正常。&lt;/p&gt;
&lt;h5 id=&quot;node-节点&quot;&gt;&lt;a href=&quot;#node-%E8%8A%82%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Node 节点&lt;/h5&gt;
&lt;p&gt;Node 是集群中的一个节点，节点也有一个名称，默认是随机分配的。默认节点会去加入一个名称为 elasticsearch 的集群。如果直接启动一堆节点，那么它们会自动组成一个 elasticsearch 集群，当然一个节点也可以组成 elasticsearch 集群。&lt;/p&gt;
&lt;h5 id=&quot;document--field&quot;&gt;&lt;a href=&quot;#document--field&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Document &amp;#x26; field&lt;/h5&gt;
&lt;p&gt;文档是 ES 中最小的数据单元，一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据，通常用 json 数据结构来表示。每个 index 下的 type，都可以存储多条 document。一个 document 里面有多个 field，每个 field 就是一个数据字段。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;74615827719340050000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`{
   &amp;quot;product_id&amp;quot;: &amp;quot;1&amp;quot;,
   &amp;quot;product_name&amp;quot;: &amp;quot;iPhone X&amp;quot;,
   &amp;quot;product_desc&amp;quot;: &amp;quot;苹果手机&amp;quot;,
   &amp;quot;category_id&amp;quot;: &amp;quot;2&amp;quot;,
   &amp;quot;category_name&amp;quot;: &amp;quot;电子产品&amp;quot;
}`, `74615827719340050000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;json&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;json&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;product_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;product_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;iPhone X&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;product_desc&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;苹果手机&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;category_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;category_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;电子产品&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;index&quot;&gt;&lt;a href=&quot;#index&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Index&lt;/h5&gt;
&lt;p&gt;索引包含了一堆有相似结构的文档数据，比如商品索引。一个索引包含很多 document，一个索引就代表了一类相似或者相同的 document。&lt;/p&gt;
&lt;h5 id=&quot;type&quot;&gt;&lt;a href=&quot;#type&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Type&lt;/h5&gt;
&lt;p&gt;类型，每个索引里可以有一个或者多个 type，type 是 index 的一个逻辑分类，比如商品 index 下有多个 type：日化商品 type、电器商品 type、生鲜商品 type。每个 type 下的 document 的 field 可能不太一样。&lt;/p&gt;
&lt;h5 id=&quot;shard&quot;&gt;&lt;a href=&quot;#shard&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;shard&lt;/h5&gt;
&lt;p&gt;单台机器无法存储大量数据，ES 可以将一个索引中的数据切分为多个 shard，分布在多台服务器上存储。有了 shard 就可以横向扩展，存储更多数据，让搜索和分析等操作分布到多台服务器上去执行，提升吞吐量和性能。每个 shard 都是一个 lucene index。&lt;/p&gt;
&lt;h5 id=&quot;replica&quot;&gt;&lt;a href=&quot;#replica&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;replica&lt;/h5&gt;
&lt;p&gt;任何一个服务器随时可能故障或宕机，此时 shard 可能就会丢失，因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务，保证数据不丢失，多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard（建立索引时一次设置，不能修改，默认 5 个），replica shard（随时修改数量，默认 1 个），默认每个索引 10 个 shard，5 个 primary shard，5 个 replica shard，最小的高可用配置，是 2 台服务器。&lt;/p&gt;
&lt;p&gt;这么说吧，shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard，而 replica shard 一般简称为 replica。&lt;/p&gt;
&lt;h4 id=&quot;es-核心概念-vs-db-核心概念&quot;&gt;&lt;a href=&quot;#es-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5-vs-db-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 核心概念 vs. DB 核心概念&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ES&lt;/th&gt;
&lt;th&gt;DB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;数据库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;数据表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;document&lt;/td&gt;
&lt;td&gt;一行数据&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;es-的分布式架构原理？&quot;&gt;&lt;a href=&quot;#es-%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84%E5%8E%9F%E7%90%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 的分布式架构原理？&lt;/h3&gt;
&lt;p&gt;ElasticSearch 设计的理念就是分布式搜索引擎，底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 ES 进程实例，组成了一个 ES 集群。&lt;/p&gt;
&lt;p&gt;ES 中存储数据的基本单位是索引，比如说你现在要在 ES 中存储一些订单数据，你就应该在 ES 中创建一个索引 &lt;code class=&quot;language-text&quot;&gt;order_idx&lt;/code&gt;，所有的订单数据就都写到这个索引里面去，一个索引差不多就是相当于是 mysql 里的一个数据库。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69710493237947290000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`index -&gt; type -&gt; mapping -&gt; document -&gt; field`, `69710493237947290000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;index -&amp;gt; type -&amp;gt; mapping -&amp;gt; document -&amp;gt; field&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样吧，为了做个更直白的介绍，我在这里做个类比。但是切记，不要划等号，类比只是为了便于理解。&lt;/p&gt;
&lt;p&gt;index 相当于 mysql 数据库。而 type 没法跟 mysql 里去对比，一个 index 里可以有多个 type，每个 type 的字段都是差不多的，但是有一些略微的差别。假设有一个 index，是订单 index，里面专门是放订单数据的。就好比说你在 mysql 中建表，有些订单是实物商品的订单，比如一件衣服、一双鞋子；有些订单是虚拟商品的订单，比如游戏点卡，话费充值。就两种订单大部分字段是一样的，但是少部分字段可能有略微的一些差别。&lt;/p&gt;
&lt;p&gt;所以就会在订单 index 里，建两个 type，一个是实物商品订单 type，一个是虚拟商品订单 type，这两个 type 大部分字段是一样的，少部分字段是不一样的。&lt;/p&gt;
&lt;p&gt;很多情况下，一个 index 里可能就一个 type，但是确实如果说是一个 index 里有多个 type 的情况（注意，mapping types 这个概念在 ElasticSearch 7.X 已被完全移除，详细说明可以参考官方文档），你可以认为 index 是一个类别的表，具体的每个 type 代表了 mysql 中的一个表。每个 type 有一个 mapping，如果你认为一个 type 是具体的一个表，index 就代表多个 type 同属于的一个类型，而 mapping 就是这个 type 的表结构定义，你在 mysql 中创建一个表，肯定是要定义表结构的，里面有哪些字段，每个字段是什么类型。实际上你往 index 里的一个 type 里面写的一条数据，叫做一条 document，一条 document 就代表了 mysql 中某个表里的一行，每个 document 有多个 field，每个 field 就代表了这个 document 中的一个字段的值。&lt;/p&gt;
&lt;p&gt;你搞一个索引，这个索引可以拆分成多个 shard，每个 shard 存储部分数据。拆分多个 shard 是有好处的，一是支持横向扩展，比如你数据量是 3T，3 个 shard，每个 shard 就 1T 的数据，若现在数据量增加到 4T，怎么扩展，很简单，重新建一个有 4 个 shard 的索引，将数据导进去；二是提高性能，数据分布在多个 shard，即多台服务器上，所有的操作，都会在多台机器上并行分布式执行，提高了吞吐量和性能。&lt;/p&gt;
&lt;p&gt;接着就是这个 shard 的数据实际是有多个备份，就是说每个 shard 都有一个 primary shard，负责写入数据，但是还有几个 replica shard。 primary shard 写入数据之后，会将数据同步到其他几个 replica shard 上去。&lt;/p&gt;
&lt;p&gt;通过这个 replica 的方案，每个 shard 的数据都有多个备份，如果某个机器宕机了，没关系啊，还有别的数据副本在别的机器上呢。高可用了吧。&lt;/p&gt;
&lt;p&gt;ES 集群多个节点，会自动选举一个节点为 master 节点，这个 master 节点其实就是干一些管理的工作的，比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了，那么会重新选举一个节点为 master 节点。&lt;/p&gt;
&lt;p&gt;如果是非 master 节点宕机了，那么会由 master 节点，让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器，重启了之后，master 节点会控制将缺失的 replica shard 分配过去，同步后续修改的数据之类的，让集群恢复正常。&lt;/p&gt;
&lt;p&gt;说得更简单一点，就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好，master 会让 primary shard 对应的 replica shard（在其他机器上）切换为 primary shard。如果宕机的机器修复了，修复后的节点也不再是 primary shard，而是 replica shard。&lt;/p&gt;
&lt;p&gt;其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。&lt;/p&gt;
&lt;h3 id=&quot;es-写入数据的工作原理是什么？&quot;&gt;&lt;a href=&quot;#es-%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 写入数据的工作原理是什么？&lt;/h3&gt;
&lt;h4 id=&quot;es-写数据过程&quot;&gt;&lt;a href=&quot;#es-%E5%86%99%E6%95%B0%E6%8D%AE%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;es 写数据过程&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;客户端选择一个 node 发送请求过去，这个 node 就是 coordinating node（协调节点）。&lt;/li&gt;
&lt;li&gt;coordinating node 对 document 进行路由，将请求转发给对应的 node（有 primary shard）。&lt;/li&gt;
&lt;li&gt;实际的 node 上的 primary shard 处理请求，然后将数据同步到 replica node。&lt;/li&gt;
&lt;li&gt;coordinating node 如果发现 primary node 和所有 replica node 都搞定之后，就返回响应结果给客户端。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;es-读数据过程&quot;&gt;&lt;a href=&quot;#es-%E8%AF%BB%E6%95%B0%E6%8D%AE%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;es 读数据过程&lt;/h4&gt;
&lt;p&gt;可以通过 doc id 来查询，会根据 doc id 进行 hash，判断出来当时把 doc id 分配到了哪个 shard 上面去，从那个 shard 去查询。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端发送请求到任意一个 node，成为 coordinate node 。&lt;/li&gt;
&lt;li&gt;coordinate node 对 doc id 进行哈希路由，将请求转发到对应的 node，此时会使用 round-robin 随机轮询算法，在 primary shard 以及其所有 replica 中随机选择一个，让读请求负载均衡。&lt;/li&gt;
&lt;li&gt;接收请求的 node 返回 document 给 coordinate node 。&lt;/li&gt;
&lt;li&gt;coordinate node 返回 document 给客户端。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;es-搜索数据过程&quot;&gt;&lt;a href=&quot;#es-%E6%90%9C%E7%B4%A2%E6%95%B0%E6%8D%AE%E8%BF%87%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;es 搜索数据过程&lt;/h4&gt;
&lt;p&gt;es 最强大的是做全文检索，就是比如你有三条数据：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;87485530061854260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`java真好玩儿啊
java好难学啊
j2ee特别牛`, `87485530061854260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;java真好玩儿啊
java好难学啊
j2ee特别牛&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你根据 java 关键词来搜索，将包含 java 的 document 给搜索出来。es 就会给你返回：java 真好玩儿啊，java 好难学啊。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端发送请求到一个 coordinate node。&lt;/li&gt;
&lt;li&gt;协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard，都可以。&lt;/li&gt;
&lt;li&gt;query phase：每个 shard 将自己的搜索结果（其实就是一些 doc id ）返回给协调节点，由协调节点进行数据的合并、排序、分页等操作，产出最终结果。&lt;/li&gt;
&lt;li&gt;fetch phase：接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据，最终返回给客户端。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;写请求是写入 primary shard，然后同步给所有的 replica shard；读请求可以从 primary shard 或 replica shard 读取，采用的是随机轮询算法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;写数据底层原理&quot;&gt;&lt;a href=&quot;#%E5%86%99%E6%95%B0%E6%8D%AE%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;写数据底层原理&lt;/h4&gt;
&lt;p&gt;先写入内存 buffer，在 buffer 里的时候数据是搜索不到的；同时将数据写入 translog 日志文件。&lt;/p&gt;
&lt;p&gt;如果 buffer 快满了，或者到一定时间，就会将内存 buffer 数据 refresh 到一个新的 segment file 中，但是此时数据不是直接进入 segment file 磁盘文件，而是先进入 os cache。这个过程就是 refresh。&lt;/p&gt;
&lt;p&gt;每隔 1 秒钟，es 将 buffer 中的数据写入一个新的 segment file，每秒钟会产生一个新的磁盘文件 segment file，这个 segment file 中就存储最近 1 秒内 buffer 中写入的数据。&lt;/p&gt;
&lt;p&gt;但是如果 buffer 里面此时没有数据，那当然不会执行 refresh 操作，如果 buffer 里面有数据，默认 1 秒钟执行一次 refresh 操作，刷入一个新的 segment file 中。&lt;/p&gt;
&lt;p&gt;操作系统里面，磁盘文件其实都有一个东西，叫做 os cache，即操作系统缓存，就是说数据写入磁盘文件之前，会先进入 os cache，先进入操作系统级别的一个内存缓存中去。只要 buffer 中的数据被 refresh 操作刷入 os cache 中，这个数据就可以被搜索到了。&lt;/p&gt;
&lt;p&gt;为什么叫 es 是准实时的？NRT，全称 near real-time。默认是每隔 1 秒 refresh 一次的，所以 es 是准实时的，因为写入的数据 1 秒之后才能被看到。可以通过 es 的 restful api 或者 java api，手动执行一次 refresh 操作，就是手动将 buffer 中的数据刷入 os cache 中，让数据立马就可以被搜索到。只要数据被输入 os cache 中，buffer 就会被清空了，因为不需要保留 buffer 了，数据在 translog 里面已经持久化到磁盘一份了。&lt;/p&gt;
&lt;p&gt;重复上面的步骤，新的数据不断进入 buffer 和 translog，不断将 buffer 数据写入一个又一个新的 segment file 中去，每次 refresh 完 buffer 清空，translog 保留。随着这个过程推进，translog 会变得越来越大。当 translog 达到一定长度的时候，就会触发 commit 操作。&lt;/p&gt;
&lt;p&gt;commit 操作发生第一步，就是将 buffer 中现有数据 refresh 到 os cache 中去，清空 buffer。然后，将一个 commit point 写入磁盘文件，里面标识着这个 commit point 对应的所有 segment file，同时强行将 os cache 中目前所有的数据都 fsync 到磁盘文件中去。最后清空现有 translog 日志文件，重启一个 translog，此时 commit 操作完成。&lt;/p&gt;
&lt;p&gt;这个 commit 操作叫做 flush。默认 30 分钟自动执行一次 flush，但如果 translog 过大，也会触发 flush。flush 操作就对应着 commit 的全过程，我们可以通过 es api，手动执行 flush 操作，手动将 os cache 中的数据 fsync 强刷到磁盘上去。&lt;/p&gt;
&lt;p&gt;translog 日志文件的作用是什么？你执行 commit 操作之前，数据要么是停留在 buffer 中，要么是停留在 os cache 中，无论是 buffer 还是 os cache 都是内存，一旦这台机器死了，内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 translog 中，一旦此时机器宕机，再次重启的时候，es 会自动读取 translog 日志文件中的数据，恢复到内存 buffer 和 os cache 中去。&lt;/p&gt;
&lt;p&gt;translog 其实也是先写入 os cache 的，默认每隔 5 秒刷一次到磁盘中去，所以默认情况下，可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中，如果此时机器挂了，会丢失 5 秒钟的数据。但是这样性能比较好，最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 fsync 到磁盘，但是性能会差很多。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;index.translog.sync_interval&lt;/code&gt; 控制 translog 多久 fsync 到磁盘，最小为 100ms；&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;index.translog.durability&lt;/code&gt; translog 是每 5 秒钟刷新一次还是每次请求都 fsync，这个参数有 2 个取值：request（每次请求都执行 fsync，es 要等 translog fsync 到磁盘后才会返回成功）和 async（默认值，translog 每隔 5 秒钟 fsync 一次）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际上你在这里，如果面试官没有问你 es 丢数据的问题，你可以在这里给面试官炫一把，你说，其实 es 第一是准实时的，数据写入 1 秒后可以搜索到；可能会丢失数据的。有 5 秒的数据，停留在 buffer、translog os cache、segment file os cache 中，而不在磁盘上，此时如果宕机，会导致 5 秒的数据丢失。&lt;/p&gt;
&lt;p&gt;总结一下，数据先写入内存 buffer，然后每隔 1s，将数据 refresh 到 os cache，到了 os cache 数据就能被搜索到（所以我们才说 es 从写入到能被搜索到，中间有 1s 的延迟）。每隔 5s，将数据写入 translog 文件（这样如果机器宕机，内存数据全没，最多会有 5s 的数据丢失），translog 大到一定程度，或者默认每隔 30min，会触发 commit 操作，将缓冲区的数据都 flush 到 segment file 磁盘文件中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;数据写入 segment file 之后，同时就建立好了倒排索引。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;删除更新数据底层原理&quot;&gt;&lt;a href=&quot;#%E5%88%A0%E9%99%A4%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;删除/更新数据底层原理&lt;/h4&gt;
&lt;p&gt;如果是删除操作，commit 的时候会生成一个 .del 文件，里面将某个 doc 标识为 deleted 状态，那么搜索的时候根据 .del 文件就知道这个 doc 是否被删除了。&lt;/p&gt;
&lt;p&gt;如果是更新操作，就是将原来的 doc 标识为 deleted 状态，然后新写入一条数据。&lt;/p&gt;
&lt;p&gt;buffer 每 refresh 一次，就会产生一个 segment file，所以默认情况下是 1 秒钟一个 segment file，这样下来 segment file 会越来越多，此时会定期执行 merge。每次 merge 的时候，会将多个 segment file 合并成一个，同时这里会将标识为 deleted 的 doc 给物理删除掉，然后将新的 segment file 写入磁盘，这里会写一个 commit point，标识所有新的 segment file，然后打开 segment file 供搜索使用，同时删除旧的 segment file。&lt;/p&gt;
&lt;h4 id=&quot;底层-lucene&quot;&gt;&lt;a href=&quot;#%E5%BA%95%E5%B1%82-lucene&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;底层 lucene&lt;/h4&gt;
&lt;p&gt;简单来说，lucene 就是一个 jar 包，里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候，引入 lucene jar，然后基于 lucene 的 api 去开发就可以了。&lt;/p&gt;
&lt;p&gt;通过 lucene，我们可以将已有的数据建立索引，lucene 会在本地磁盘上面，给我们组织索引的数据结构。&lt;/p&gt;
&lt;h4 id=&quot;倒排索引&quot;&gt;&lt;a href=&quot;#%E5%80%92%E6%8E%92%E7%B4%A2%E5%BC%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;倒排索引&lt;/h4&gt;
&lt;p&gt;在搜索引擎中，每个文档都有一个对应的文档 ID，文档内容被表示为一系列关键词的集合。例如，文档 1 经过分词，提取了 20 个关键词，每个关键词都会记录它在文档中出现的次数和出现位置。&lt;/p&gt;
&lt;p&gt;那么，倒排索引就是关键词到文档 ID 的映射，每个关键词都对应着一系列的文件，这些文件中都出现了关键词。&lt;/p&gt;
&lt;p&gt;举个栗子。&lt;/p&gt;
&lt;p&gt;有以下文档：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DocId&lt;/th&gt;
&lt;th&gt;Doc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;谷歌地图之父跳槽 Facebook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;谷歌地图之父加盟 Facebook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;谷歌地图创始人拉斯离开谷歌加盟 Facebook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;谷歌地图之父拉斯加盟社交网站 Facebook&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对文档进行分词之后，得到以下倒排索引。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WordId&lt;/th&gt;
&lt;th&gt;Word&lt;/th&gt;
&lt;th&gt;DocIds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;谷歌&lt;/td&gt;
&lt;td&gt;1, 2, 3, 4, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;地图&lt;/td&gt;
&lt;td&gt;1, 2, 3, 4, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;之父&lt;/td&gt;
&lt;td&gt;1, 2, 4, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;跳槽&lt;/td&gt;
&lt;td&gt;1, 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;1, 2, 3, 4, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;加盟&lt;/td&gt;
&lt;td&gt;2, 3, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;创始人&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;拉斯&lt;/td&gt;
&lt;td&gt;3, 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;离开&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;与&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;..&lt;/td&gt;
&lt;td&gt;..&lt;/td&gt;
&lt;td&gt;..&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;另外，实用的倒排索引还可以记录更多的信息，比如文档频率信息，表示在文档集合中有多少个文档包含某个单词。&lt;/p&gt;
&lt;p&gt;那么，有了倒排索引，搜索引擎可以很方便地响应用户的查询。比如用户输入查询 Facebook ，搜索系统查找倒排索引，从中读出包含这个单词的文档，这些文档就是提供给用户的搜索结果。&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;blockquote&gt;
&lt;p&gt;上面只是一个简单的栗子，并没有严格按照字典顺序升序排列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;es-在数据量很大的情况下（数十亿级别）如何提高查询效率？&quot;&gt;&lt;a href=&quot;#es-%E5%9C%A8%E6%95%B0%E6%8D%AE%E9%87%8F%E5%BE%88%E5%A4%A7%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%88%E6%95%B0%E5%8D%81%E4%BA%BF%E7%BA%A7%E5%88%AB%EF%BC%89%E5%A6%82%E4%BD%95%E6%8F%90%E9%AB%98%E6%9F%A5%E8%AF%A2%E6%95%88%E7%8E%87%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 在数据量很大的情况下（数十亿级别）如何提高查询效率？&lt;/h3&gt;
&lt;h4 id=&quot;性能优化的杀手锏-filesystem-cache&quot;&gt;&lt;a href=&quot;#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%9A%84%E6%9D%80%E6%89%8B%E9%94%8F-filesystem-cache&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;性能优化的杀手锏 filesystem cache&lt;/h4&gt;
&lt;p&gt;你往 es 里写的数据，实际上都写到磁盘文件里去了，查询的时候，操作系统会将磁盘文件里的数据自动缓存到 filesystem cache 里面去。&lt;/p&gt;
&lt;p&gt;es 的搜索引擎严重依赖于底层的 filesystem cache，你如果给 filesystem cache 更多的内存，尽量让内存可以容纳所有的 idx segment file 索引数据文件，那么你搜索的时候就基本都是走内存的，性能会非常高。&lt;/p&gt;
&lt;p&gt;性能差距究竟可以有多大？我们之前很多的测试和压测，如果走磁盘一般肯定上秒，搜索性能绝对是秒级别的，1 秒、5 秒、10 秒。但如果是走 filesystem cache，是走纯内存的，那么一般来说性能比走磁盘要高一个数量级，基本上就是毫秒级的，从几毫秒到几百毫秒不等。&lt;/p&gt;
&lt;p&gt;这里有个真实的案例。某个公司 es 节点有 3 台机器，每台机器看起来内存很多，64G，总内存就是 &lt;code class=&quot;language-text&quot;&gt;64 * 3 = 192G&lt;/code&gt;。每台机器给 es jvm heap 是 32G，那么剩下来留给 filesystem cache 的就是每台机器才 32G，总共集群里给 filesystem cache 的就是 &lt;code class=&quot;language-text&quot;&gt;32 * 3 = 96G&lt;/code&gt; 内存。而此时，整个磁盘上索引数据文件，在 3 台机器上一共占用了 1T 的磁盘容量，es 数据量是 1T，那么每台机器的数据量是 300G。这样性能好吗？filesystem cache 的内存才 100G，十分之一的数据可以放内存，其他的都在磁盘，然后你执行搜索操作，大部分操作都是走磁盘，性能肯定差。&lt;/p&gt;
&lt;p&gt;归根结底，你要让 es 性能要好，最佳的情况下，就是你的机器的内存，至少可以容纳你的总数据量的一半。&lt;/p&gt;
&lt;p&gt;根据我们自己的生产环境实践经验，最佳的情况下，是仅仅在 es 中就存少量的数据，就是你要用来搜索的那些索引，如果内存留给 filesystem cache 的是 100G，那么你就将索引数据控制在 100G 以内，这样的话，你的数据几乎全部走内存来搜索，性能非常之高，一般可以在 1 秒以内。&lt;/p&gt;
&lt;p&gt;比如说你现在有一行数据。id,name,age… 30 个字段。但是你现在搜索，只需要根据 id,name,age 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段，就会导致说 90% 的数据是不用来搜索的，结果硬是占据了 es 机器上的 filesystem cache 的空间，单条数据的数据量越大，就会导致 filesystem cahce 能缓存的数据就越少。其实，仅仅写入 es 中要用来检索的少数几个字段就可以了，比如说就写入 es id,name,age 三个字段，然后你可以把其他的字段数据存在 mysql/hbase 里，我们一般是建议用 es + hbase 这么一个架构。&lt;/p&gt;
&lt;p&gt;hbase 的特点是适用于海量数据的在线存储，就是对 hbase 可以写入海量数据，但是不要做复杂的搜索，做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索，拿到的结果可能就 20 个 doc id，然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据，给查出来，再返回给前端。&lt;/p&gt;
&lt;p&gt;写入 es 的数据最好小于等于，或者是略微大于 es 的 filesystem cache 的内存容量。然后你从 es 检索可能就花费 20ms，然后再根据 es 返回的 id 去 hbase 里查询，查 20 条数据，可能也就耗费个 30ms，可能你原来那么玩儿，1T 数据都放 es，会每次查询都是 5~10s，现在可能性能就会很高，每次查询就是 50ms。&lt;/p&gt;
&lt;h4 id=&quot;数据预热&quot;&gt;&lt;a href=&quot;#%E6%95%B0%E6%8D%AE%E9%A2%84%E7%83%AD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;数据预热&lt;/h4&gt;
&lt;p&gt;假如说，哪怕是你就按照上述的方案去做了，es 集群中每个机器写入的数据量还是超过了 filesystem cache 一倍，比如说你写入一台机器 60G 数据，结果 filesystem cache 就 30G，还是有 30G 数据留在了磁盘上。&lt;/p&gt;
&lt;p&gt;其实可以做数据预热。&lt;/p&gt;
&lt;p&gt;举个例子，拿微博来说，你可以把一些大 V，平时看的人很多的数据，你自己提前后台搞个系统，每隔一会儿，自己的后台系统去搜索一下热数据，刷到 filesystem cache 里去，后面用户实际上来看这个热数据的时候，他们就是直接从内存里搜索了，很快。&lt;/p&gt;
&lt;p&gt;或者是电商，你可以将平时查看最多的一些商品，比如说 iphone 8 热数据提前后台搞个程序，每隔 1 分钟自己主动访问一次，刷到 filesystem cache 里去。&lt;/p&gt;
&lt;p&gt;对于那些你觉得比较热的、经常会有人访问的数据，最好做一个专门的缓存预热子系统，就是对热数据每隔一段时间，就提前访问一下，让数据进入 filesystem cache 里面去。这样下次别人访问的时候，性能一定会好很多。&lt;/p&gt;
&lt;h4 id=&quot;冷热分离&quot;&gt;&lt;a href=&quot;#%E5%86%B7%E7%83%AD%E5%88%86%E7%A6%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;冷热分离&lt;/h4&gt;
&lt;p&gt;es 可以做类似于 mysql 的水平拆分，就是说将大量的访问很少、频率很低的数据，单独写一个索引，然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中，然后热数据写入另外一个索引中，这样可以确保热数据在被预热之后，尽量都让他们留在 filesystem os cache 里，别让冷数据给冲刷掉。&lt;/p&gt;
&lt;p&gt;你看，假设你有 6 台机器，2 个索引，一个放冷数据，一个放热数据，每个索引 3 个 shard。3 台机器放热数据 index，另外 3 台机器放冷数据 index。然后这样的话，你大量的时间是在访问热数据 index，热数据可能就占总数据量的 10%，此时数据量很少，几乎全都保留在 filesystem cache 里面了，就可以确保热数据的访问性能是很高的。但是对于冷数据而言，是在别的 index 里的，跟热数据 index 不在相同的机器上，大家互相之间都没什么联系了。如果有人访问冷数据，可能大量数据是在磁盘上的，此时性能差点，就 10% 的人去访问冷数据，90% 的人在访问热数据，也无所谓了。&lt;/p&gt;
&lt;h4 id=&quot;document-模型设计&quot;&gt;&lt;a href=&quot;#document-%E6%A8%A1%E5%9E%8B%E8%AE%BE%E8%AE%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;document 模型设计&lt;/h4&gt;
&lt;p&gt;对于 MySQL，我们经常有一些复杂的关联查询。在 es 里该怎么玩儿，es 里面的复杂的关联查询尽量别用，一旦用了性能一般都不太好。&lt;/p&gt;
&lt;p&gt;最好是先在 Java 系统里就完成关联，将关联好的数据直接写入 es 中。搜索的时候，就不需要利用 es 的搜索语法来完成 join 之类的关联搜索了。&lt;/p&gt;
&lt;p&gt;document 模型设计是非常重要的，很多操作，不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。es 能支持的操作就那么多，不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作，尽量在 document 模型设计的时候，写入的时候就完成。另外对于一些太复杂的操作，比如 join/nested/parent-child 搜索都要尽量避免，性能都很差的。&lt;/p&gt;
&lt;h4 id=&quot;分页性能优化&quot;&gt;&lt;a href=&quot;#%E5%88%86%E9%A1%B5%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分页性能优化&lt;/h4&gt;
&lt;p&gt;es 的分页是较坑的，为啥呢？举个例子吧，假如你每页是 10 条数据，你现在要查询第 100 页，实际上是会把每个 shard 上存储的前 1000 条数据都查到一个协调节点上，如果你有个 5 个 shard，那么就有 5000 条数据，接着协调节点对这 5000 条数据进行一些合并、处理，再获取到最终第 100 页的 10 条数据。&lt;/p&gt;
&lt;p&gt;分布式的，你要查第 100 页的 10 条数据，不可能说从 5 个 shard，每个 shard 就查 2 条数据，最后到协调节点合并成 10 条数据吧？你必须得从每个 shard 都查 1000 条数据过来，然后根据你的需求进行排序、筛选等等操作，最后再次分页，拿到里面第 100 页的数据。你翻页的时候，翻的越深，每个 shard 返回的数据就越多，而且协调节点处理的时间越长，非常坑爹。所以用 es 做分页的时候，你会发现越翻到后面，就越是慢。&lt;/p&gt;
&lt;p&gt;我们之前也是遇到过这个问题，用 es 作分页，前几页就几十毫秒，翻到 10 页或者几十页的时候，基本上就要 5~10 秒才能查出来一页数据了。&lt;/p&gt;
&lt;p&gt;有什么解决方案吗？&lt;/p&gt;
&lt;h5 id=&quot;不允许深度分页（默认深度分页性能很差）&quot;&gt;&lt;a href=&quot;#%E4%B8%8D%E5%85%81%E8%AE%B8%E6%B7%B1%E5%BA%A6%E5%88%86%E9%A1%B5%EF%BC%88%E9%BB%98%E8%AE%A4%E6%B7%B1%E5%BA%A6%E5%88%86%E9%A1%B5%E6%80%A7%E8%83%BD%E5%BE%88%E5%B7%AE%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;不允许深度分页（默认深度分页性能很差）&lt;/h5&gt;
&lt;p&gt;跟产品经理说，你系统不允许翻那么深的页，默认翻的越深，性能就越差。&lt;/p&gt;
&lt;h5 id=&quot;类似于-app-里的推荐商品不断下拉出来一页一页的&quot;&gt;&lt;a href=&quot;#%E7%B1%BB%E4%BC%BC%E4%BA%8E-app-%E9%87%8C%E7%9A%84%E6%8E%A8%E8%8D%90%E5%95%86%E5%93%81%E4%B8%8D%E6%96%AD%E4%B8%8B%E6%8B%89%E5%87%BA%E6%9D%A5%E4%B8%80%E9%A1%B5%E4%B8%80%E9%A1%B5%E7%9A%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;类似于 app 里的推荐商品不断下拉出来一页一页的&lt;/h5&gt;
&lt;p&gt;类似于微博中，下拉刷微博，刷出来一页一页的，你可以用 scroll api，关于如何使用，自行上网搜索。&lt;/p&gt;
&lt;p&gt;scroll 会一次性给你生成所有数据的一个快照，然后每次滑动向后翻页就是通过游标 &lt;code class=&quot;language-text&quot;&gt;scroll_id&lt;/code&gt; 移动，获取下一页这样子，性能会比上面说的那种分页性能要高很多很多，基本上都是毫秒级的。&lt;/p&gt;
&lt;p&gt;但是，唯一的一点就是，这个适合于那种类似微博下拉翻页的，不能随意跳到任何一页的场景。也就是说，你不能先进入第 10 页，然后去第 120 页，然后又回到第 58 页，不能随意乱跳页。所以现在很多产品，都是不允许你随意翻页的，app，也有一些网站，做的就是你只能往下拉，一页一页的翻。&lt;/p&gt;
&lt;p&gt;初始化时必须指定 scroll 参数，告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时，否则可能因为超时而失败。&lt;/p&gt;
&lt;p&gt;除了用 scroll api，你也可以用 &lt;code class=&quot;language-text&quot;&gt;search_after&lt;/code&gt; 来做，&lt;code class=&quot;language-text&quot;&gt;search_after&lt;/code&gt; 的思想是使用前一页的结果来帮助检索下一页的数据，显然，这种方式也不允许你随意翻页，你只能一页页往后翻。初始化时，需要使用一个唯一值的字段作为 sort 字段。&lt;/p&gt;
&lt;h3 id=&quot;es-生产集群的部署架构是什么？&quot;&gt;&lt;a href=&quot;#es-%E7%94%9F%E4%BA%A7%E9%9B%86%E7%BE%A4%E7%9A%84%E9%83%A8%E7%BD%B2%E6%9E%B6%E6%9E%84%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ES 生产集群的部署架构是什么？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;es 生产集群我们部署了 5 台机器，每台机器是 6 核 64G 的，集群总内存是 320G。&lt;/li&gt;
&lt;li&gt;我们 es 集群的日增量数据大概是 2000 万条，每天日增量数据大概是 500MB，每月增量数据大概是 6 亿，15G。目前系统已经运行了几个月，现在 es 集群里数据总量大概是 100G 左右。&lt;/li&gt;
&lt;li&gt;目前线上有 5 个索引（这个结合你们自己业务来，看看自己有哪些数据可以放 es 的），每个索引的数据量大概是 20G，所以这个数据量之内，我们每个索引分配的是 8 个 shard，比默认的 5 个 shard 多了 3 个 shard。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;缓存&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存&lt;/h2&gt;
&lt;h3 id=&quot;项目中缓存是如何使用的？&quot;&gt;&lt;a href=&quot;#%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%BC%93%E5%AD%98%E6%98%AF%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;项目中缓存是如何使用的？&lt;/h3&gt;
&lt;p&gt;这个，需要结合自己项目的业务来。&lt;/p&gt;
&lt;h4 id=&quot;为什么要用缓存？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8%E7%BC%93%E5%AD%98%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为什么要用缓存？&lt;/h4&gt;
&lt;p&gt;用缓存，主要有两个用途：高性能、高并发。&lt;/p&gt;
&lt;h5 id=&quot;高性能&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E6%80%A7%E8%83%BD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高性能&lt;/h5&gt;
&lt;p&gt;假设这么个场景，你有个操作，一个请求过来，吭哧吭哧你各种乱七八糟操作 mysql，半天查出来一个结果，耗时 600ms。但是这个结果可能接下来几个小时都不会变了，或者变了也可以不用立即反馈给用户。那么此时咋办？&lt;/p&gt;
&lt;p&gt;缓存啊，折腾 600ms 查出来的结果，扔缓存里，一个 key 对应一个 value，下次再有人查，别走 mysql 折腾 600ms 了，直接从缓存里，通过一个 key 查出来一个 value，2ms 搞定。性能提升 300 倍。&lt;/p&gt;
&lt;p&gt;就是说对于一些需要复杂操作耗时查出来的结果，且确定后面不怎么变化，但是有很多读请求，那么直接将查询出来的结果放在缓存中，后面直接读缓存就好。&lt;/p&gt;
&lt;h4 id=&quot;高并发&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E5%B9%B6%E5%8F%91&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高并发&lt;/h4&gt;
&lt;p&gt;mysql 这么重的数据库，压根儿设计不是让你玩儿高并发的，虽然也可以玩儿，但是天然支持不好。mysql 单机支撑到 2000QPS 也开始容易报警了。&lt;/p&gt;
&lt;p&gt;所以要是你有个系统，高峰期一秒钟过来的请求有 1 万，那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存，把很多数据放缓存，别放 mysql。缓存功能简单，说白了就是 key-value 式操作，单机支撑的并发量轻松一秒几万十几万，支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。&lt;/p&gt;
&lt;p&gt;缓存是走内存的，内存天然就支撑高并发。&lt;/p&gt;
&lt;h4 id=&quot;用了缓存之后会有什么不良后果？&quot;&gt;&lt;a href=&quot;#%E7%94%A8%E4%BA%86%E7%BC%93%E5%AD%98%E4%B9%8B%E5%90%8E%E4%BC%9A%E6%9C%89%E4%BB%80%E4%B9%88%E4%B8%8D%E8%89%AF%E5%90%8E%E6%9E%9C%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;用了缓存之后会有什么不良后果？&lt;/h4&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;h3 id=&quot;redis-和-memcached-有什么区别？&quot;&gt;&lt;a href=&quot;#redis-%E5%92%8C-memcached-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 和 Memcached 有什么区别？&lt;/h3&gt;
&lt;h4 id=&quot;区别&quot;&gt;&lt;a href=&quot;#%E5%8C%BA%E5%88%AB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;区别&lt;/h4&gt;
&lt;h5 id=&quot;redis-支持复杂的数据结构&quot;&gt;&lt;a href=&quot;#redis-%E6%94%AF%E6%8C%81%E5%A4%8D%E6%9D%82%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 支持复杂的数据结构&lt;/h5&gt;
&lt;p&gt;Redis 相比 Memcached 来说，拥有更多的数据结构，能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作，Redis 会是不错的选择。&lt;/p&gt;
&lt;h5 id=&quot;redis-原生支持集群模式&quot;&gt;&lt;a href=&quot;#redis-%E5%8E%9F%E7%94%9F%E6%94%AF%E6%8C%81%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 原生支持集群模式&lt;/h5&gt;
&lt;p&gt;在 Redis3.x 版本中，便能支持 cluster 模式，而 Memcached 没有原生的集群模式，需要依靠客户端来实现往集群中分片写入数据。&lt;/p&gt;
&lt;h4 id=&quot;性能对比&quot;&gt;&lt;a href=&quot;#%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;性能对比&lt;/h4&gt;
&lt;p&gt;由于 Redis 只使用单核，而 Memcached 可以使用多核，所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中，Memcached 性能要高于 Redis。虽然 Redis 最近也在存储大数据的性能上进行优化，但是比起 Memcached，还是稍有逊色。&lt;/p&gt;
&lt;h4 id=&quot;redis-的线程模型&quot;&gt;&lt;a href=&quot;#redis-%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 的线程模型&lt;/h4&gt;
&lt;p&gt;Redis 内部使用文件事件处理器 file event handler，这个文件事件处理器是单线程的，所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket，将产生事件的 socket 压入内存队列中，事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。&lt;/p&gt;
&lt;p&gt;文件事件处理器的结构包含 4 个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个 socket&lt;/li&gt;
&lt;li&gt;IO 多路复用程序&lt;/li&gt;
&lt;li&gt;文件事件分派器&lt;/li&gt;
&lt;li&gt;事件处理器（连接应答处理器、命令请求处理器、命令回复处理器）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多个 socket 可能会并发产生不同的操作，每个操作对应不同的文件事件，但是 IO 多路复用程序会监听多个 socket，会将产生事件的 socket 放入队列中排队，事件分派器每次从队列中取出一个 socket，根据 socket 的事件类型交给对应的事件处理器进行处理。&lt;/p&gt;
&lt;p&gt;来看客户端与 Redis 的一次通信过程：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-d55d3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50.992555831265506%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABMklEQVQoz22Rx27DMBBE+f9/Z0ENVm9Wj6oTK2p5JqGDgcyBWIqzO7MjcRzHvu/LsjwlxnEchuFLguuPRJ7nZVlSfH9C0DnPM9Q4jqMoCoKg67pIguZfiVMCGVXsF0TTNGpw27bIcqKQpqmu61mW8eS6ru/70KZpqusawnlBIMgbtlHACZ24ME3TMIyiKKA+Hg8IOEqSxLIsZh0S7+Z1XbFNRUEzVrnCvt/vnEjBcxwHL6xzu92YWFWVpmkw3zvzlTc+ERWF0sGRbdtIsTyFcseVAg0KOALN1+ulBCmUcxjqhIQF9RELTCcXWljtnTYVW4VhyDLwGLFtG29ERWDKAvA8D31Omvu+h8YscX6CeSTXXqgu8DsInAVVLlj4vxllZqNGvEiRFie+8EIijGA6+xPWH569LZLhi7LkAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 18 11 28 38&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-fee1c.png&quot; data-srcset=&quot;/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-a67b7.png 200w,
/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-0b187.png 400w,
/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-fee1c.png 800w,
/static/2023-09-18-11-28-38-1fea796f263f42bdf7c25f8599b536ea-d55d3.png 806w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;要明白，通信是通过 socket 来完成的，不懂的同学可以先去看一看 socket 网络编程。&lt;/p&gt;
&lt;p&gt;首先，Redis 服务端进程初始化的时候，会将 server socket 的 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件与连接应答处理器关联。&lt;/p&gt;
&lt;p&gt;客户端 socket01 向 Redis 进程的 server socket 请求建立连接，此时 server socket 会产生一个 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件，IO 多路复用程序监听到 server socket 产生的事件后，将该 socket 压入队列中。文件事件分派器从队列中获取 socket，交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01，并将该 socket01 的 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件与命令请求处理器关联。&lt;/p&gt;
&lt;p&gt;假设此时客户端发送了一个 set key value 请求，此时 Redis 中的 socket01 会产生 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件，IO 多路复用程序将 socket01 压入队列，此时事件分派器从队列中获取到 socket01 产生的 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件，由于前面 socket01 的 &lt;code class=&quot;language-text&quot;&gt;AE_READABLE&lt;/code&gt; 事件已经与命令请求处理器关联，因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后，它会将 socket01 的 &lt;code class=&quot;language-text&quot;&gt;AE_WRITABLE&lt;/code&gt; 事件与命令回复处理器关联。&lt;/p&gt;
&lt;p&gt;如果此时客户端准备好接收返回结果了，那么 Redis 中的 socket01 会产生一个 &lt;code class=&quot;language-text&quot;&gt;AE_WRITABLE&lt;/code&gt; 事件，同样压入队列中，事件分派器找到相关联的命令回复处理器，由命令回复处理器对 socket01 输入本次操作的一个结果，比如 ok，之后解除 socket01 的 &lt;code class=&quot;language-text&quot;&gt;AE_WRITABLE&lt;/code&gt; 事件与命令回复处理器的关联。&lt;/p&gt;
&lt;p&gt;这样便完成了一次通信。关于 Redis 的一次通信过程，推荐读者阅读 &lt;a href=&quot;https://github.com/doocs/technical-books#database&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;Redis 设计与实现——黄健宏&lt;/a&gt; 进行系统学习。&lt;/p&gt;
&lt;h4 id=&quot;为啥-redis-单线程模型也能效率这么高？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E5%95%A5-redis-%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E4%B9%9F%E8%83%BD%E6%95%88%E7%8E%87%E8%BF%99%E4%B9%88%E9%AB%98%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为啥 Redis 单线程模型也能效率这么高？&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;纯内存操作。&lt;/li&gt;
&lt;li&gt;核心是基于非阻塞的 IO 多路复用机制。&lt;/li&gt;
&lt;li&gt;C 语言实现，一般来说，C 语言实现的程序距离操作系统更近，执行速度相对会更快。&lt;/li&gt;
&lt;li&gt;单线程反而避免了多线程的频繁上下文切换问题，预防了多线程可能产生的竞争问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;redis-60-开始引入多线程&quot;&gt;&lt;a href=&quot;#redis-60-%E5%BC%80%E5%A7%8B%E5%BC%95%E5%85%A5%E5%A4%9A%E7%BA%BF%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 6.0 开始引入多线程&lt;/h4&gt;
&lt;p&gt;注意！Redis 6.0 之后的版本抛弃了单线程模型这一设计，原本使用单线程运行的 Redis 也开始选择性地使用多线程模型。&lt;/p&gt;
&lt;p&gt;前面还在强调 Redis 单线程模型的高效性，现在为什么又要引入多线程？这其实说明 Redis 在有些方面，单线程已经不具有优势了。因为读写网络的 Read/Write 系统调用在 Redis 执行期间占用了大部分 CPU 时间，如果把网络读写做成多线程的方式对性能会有很大提升。&lt;/p&gt;
&lt;p&gt;Redis 的多线程部分只是用来处理网络数据的读写和协议解析，执行命令仍然是单线程。之所以这么设计是不想 Redis 因为多线程而变得复杂，需要去控制 key、lua、事务、LPUSH/LPOP 等等的并发问题。&lt;/p&gt;
&lt;h4 id=&quot;总结&quot;&gt;&lt;a href=&quot;#%E6%80%BB%E7%BB%93&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;总结&lt;/h4&gt;
&lt;p&gt;Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈，所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本，系统的性能瓶颈也主要在网络 I/O 操作上；而 Redis 引入多线程操作也是出于性能上的考虑，对于一些大键值对的删除操作，通过多线程非阻塞地释放内存空间(释放操作不会阻塞网络 IO 读写,因为网络 IO 读写与释放的命令执行不是同一个线程)也能减少对 Redis 主线程阻塞的时间，提高执行的效率。&lt;/p&gt;
&lt;h3 id=&quot;redis-都有哪些数据类型及适用场景？&quot;&gt;&lt;a href=&quot;#redis-%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%8F%8A%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 都有哪些数据类型及适用场景？&lt;/h3&gt;
&lt;p&gt;Redis 主要有以下几种数据类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;li&gt;Hashes&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Sets&lt;/li&gt;
&lt;li&gt;Sorted Sets&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis 除了这 5 种数据类型之外，还有 Bitmaps、HyperLogLogs、Streams 等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;strings&quot;&gt;&lt;a href=&quot;#strings&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Strings&lt;/h4&gt;
&lt;p&gt;这是最简单的类型，就是普通的 set 和 get，做简单的 KV 缓存。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88554695566689520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`set college szu`, `88554695566689520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; college szu&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;hashes&quot;&gt;&lt;a href=&quot;#hashes&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hashes&lt;/h4&gt;
&lt;p&gt;这个是类似 map 的一种结构，这个一般就是可以将结构化的数据，比如一个对象（前提是这个对象没嵌套其他的对象）给缓存在 Redis 里，然后每次读写缓存的时候，可以就操作 hash 里的某个字段。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53585463679223276000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`hset person name bingo
hset person age 20
hset person id 1
hget person name`, `53585463679223276000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;hset person name bingo
hset person age &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;
hset person &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
hget person name&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28571920483087987000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`(person = {
   &amp;quot;name&amp;quot;: &amp;quot;bingo&amp;quot;,
   &amp;quot;age&amp;quot;: 20,
   &amp;quot;id&amp;quot;: 1
})`, `28571920483087987000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;json&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;json&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-json line-numbers&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;(person = &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bingo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;)&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;lists&quot;&gt;&lt;a href=&quot;#lists&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Lists&lt;/h4&gt;
&lt;p&gt;Lists 是有序列表，这个可以玩儿出很多花样。&lt;/p&gt;
&lt;p&gt;比如可以通过 list 存储一些列表型的数据结构，类似粉丝列表、文章的评论列表之类的东西。&lt;/p&gt;
&lt;p&gt;比如可以通过 lrange 命令，读取某个闭区间内的元素，可以基于 list 实现分页查询，这个是很棒的一个功能，基于 Redis 实现简单的高性能分页，可以做类似微博那种下拉不断分页的东西，性能高，就一页一页走。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17897231255717093000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`# 0 开始位置，-1 结束位置，结束位置为 -1 时，表示列表的最后一个位置，即查看所有。
lrange mylist 0 -1`, `17897231255717093000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 0 开始位置，-1 结束位置，结束位置为 -1 时，表示列表的最后一个位置，即查看所有。&lt;/span&gt;
lrange mylist &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; -1&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;比如可以搞个简单的消息队列，从 list 头怼进去，从 list 尾巴那里弄出来。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;85022743836453470000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`lpush mylist 1
lpush mylist 2
lpush mylist 3 4 5

# 1
rpop mylist`, `85022743836453470000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lpush mylist &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
lpush mylist &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
lpush mylist &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 1&lt;/span&gt;
rpop mylist&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;sets&quot;&gt;&lt;a href=&quot;#sets&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sets&lt;/h4&gt;
&lt;p&gt;Sets 是无序集合，自动去重。&lt;/p&gt;
&lt;p&gt;直接基于 set 将系统里需要去重的数据扔进去，自动就给去重了，如果你需要对一些数据进行快速的全局去重，你当然也可以基于 jvm 内存里的 HashSet 进行去重，但是如果你的某个系统部署在多台机器上呢？得基于 Redis 进行全局的 set 去重。&lt;/p&gt;
&lt;p&gt;可以基于 set 玩儿交集、并集、差集的操作，比如交集吧，可以把两个人的粉丝列表整一个交集，看看俩人的共同好友是谁？对吧。&lt;/p&gt;
&lt;p&gt;把两个大 V 的粉丝都放在两个 set 中，对两个 set 做交集。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18735570011549995000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`#-------操作一个 set-------
# 添加元素
sadd mySet 1

# 查看全部元素
smembers mySet

# 判断是否包含某个值
sismember mySet 3

# 删除某些元素
srem mySet 1
srem mySet 2 4

# 查看元素个数
scard mySet

# 随机删除一个元素
spop mySet

#-------操作多个 set-------
# 将一个 set 的元素移动到另外一个 set
smove yourSet mySet 2

# 求两 set 的交集
sinter yourSet mySet

# 求两 set 的并集
sunion yourSet mySet

# 求在 yourSet 中而不在 mySet 中的元素
sdiff yourSet mySet`, `18735570011549995000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#-------操作一个 set-------&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 添加元素&lt;/span&gt;
sadd mySet &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 查看全部元素&lt;/span&gt;
smembers mySet

&lt;span class=&quot;token comment&quot;&gt;# 判断是否包含某个值&lt;/span&gt;
sismember mySet &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 删除某些元素&lt;/span&gt;
srem mySet &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
srem mySet &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 查看元素个数&lt;/span&gt;
scard mySet

&lt;span class=&quot;token comment&quot;&gt;# 随机删除一个元素&lt;/span&gt;
spop mySet

&lt;span class=&quot;token comment&quot;&gt;#-------操作多个 set-------&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 将一个 set 的元素移动到另外一个 set&lt;/span&gt;
smove yourSet mySet &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 求两 set 的交集&lt;/span&gt;
sinter yourSet mySet

&lt;span class=&quot;token comment&quot;&gt;# 求两 set 的并集&lt;/span&gt;
sunion yourSet mySet

&lt;span class=&quot;token comment&quot;&gt;# 求在 yourSet 中而不在 mySet 中的元素&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sdiff&lt;/span&gt; yourSet mySet&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;sorted-sets&quot;&gt;&lt;a href=&quot;#sorted-sets&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sorted Sets&lt;/h4&gt;
&lt;p&gt;Sorted Sets 是排序的 set，去重但可以排序，写进去的时候给一个分数，自动根据分数排序。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;23412191293687878000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
zadd board 63 zhaoliu

# 获取排名前三的用户（默认是升序，所以需要 rev 改为降序）
zrevrange board 0 3

# 获取某用户的排名
zrank board zhaoliu`, `23412191293687878000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;zadd board &lt;span class=&quot;token number&quot;&gt;85&lt;/span&gt; zhangsan
zadd board &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt; lisi
zadd board &lt;span class=&quot;token number&quot;&gt;96&lt;/span&gt; wangwu
zadd board &lt;span class=&quot;token number&quot;&gt;63&lt;/span&gt; zhaoliu

&lt;span class=&quot;token comment&quot;&gt;# 获取排名前三的用户（默认是升序，所以需要 rev 改为降序）&lt;/span&gt;
zrevrange board &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 获取某用户的排名&lt;/span&gt;
zrank board zhaoliu&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;redis-的过期策略都有哪些？&quot;&gt;&lt;a href=&quot;#redis-%E7%9A%84%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 的过期策略都有哪些？&lt;/h3&gt;
&lt;h4 id=&quot;redis-过期策略&quot;&gt;&lt;a href=&quot;#redis-%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 过期策略&lt;/h4&gt;
&lt;p&gt;Redis 过期策略是：定期删除 + 惰性删除。&lt;/p&gt;
&lt;p&gt;所谓定期删除，指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key，检查其是否过期，如果过期就删除。&lt;/p&gt;
&lt;p&gt;假设 Redis 里放了 10w 个 key，都设置了过期时间，你每隔几百毫秒，就检查 10w 个 key，那 Redis 基本上就死了，cpu 负载会很高的，消耗在你的检查过期 key 上了。注意，这里可不是每隔 100ms 就遍历所有的设置过期时间的 key，那样就是一场性能上的灾难。实际上 Redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。&lt;/p&gt;
&lt;p&gt;但是问题是，定期删除可能会导致很多过期 key 到了时间并没有被删除掉，那咋整呢？所以就是惰性删除了。这就是说，在你获取某个 key 的时候，Redis 会检查一下，这个 key 如果设置了过期时间那么是否过期了？如果过期了此时就会删除，不会给你返回任何东西。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;获取 key 的时候，如果此时 key 已经过期，就删除，不会返回任何东西。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是实际上这还是有问题的，如果定期删除漏掉了很多过期 key，然后你也没及时去查，也就没走惰性删除，此时会怎么样？如果大量过期 key 堆积在内存里，导致 Redis 内存块耗尽了，咋整？&lt;/p&gt;
&lt;p&gt;答案是：走内存淘汰机制。&lt;/p&gt;
&lt;h4 id=&quot;内存淘汰机制&quot;&gt;&lt;a href=&quot;#%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;内存淘汰机制&lt;/h4&gt;
&lt;p&gt;Redis 内存淘汰机制有以下几个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;noeviction: 当内存不足以容纳新写入数据时，新写入操作会报错，这个一般没人用吧，实在是太恶心了。&lt;/li&gt;
&lt;li&gt;allkeys-lru：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的 key（这个是最常用的）。&lt;/li&gt;
&lt;li&gt;allkeys-random：当内存不足以容纳新写入数据时，在键空间中，随机移除某个 key，这个一般没人用吧，为啥要随机，肯定是把最近最少使用的 key 给干掉啊。&lt;/li&gt;
&lt;li&gt;volatile-lru：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，移除最近最少使用的 key（这个一般不太合适）。&lt;/li&gt;
&lt;li&gt;volatile-random：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，随机移除某个 key。&lt;/li&gt;
&lt;li&gt;volatile-ttl：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，有更早过期时间的 key 优先移除。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;手写一个-lru-算法&quot;&gt;&lt;a href=&quot;#%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA-lru-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;手写一个 LRU 算法&lt;/h4&gt;
&lt;p&gt;LRU 就是 Least Recently Used 的缩写，翻译过来就是“最近最少使用”。也就是说 LRU 算法会将最近最少用的缓存移除，让给最新使用的缓存。而往往最常读取的，也就是读取次数最多的，所以利用好 LRU 算法，我们能够提供对热点数据的缓存效率，能够提高缓存服务的内存使用率。&lt;/p&gt;
&lt;p&gt;不求自己纯手工从底层开始打造出自己的 LRU，但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29521480092770890000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class LRUCache&lt;K, V&gt; extends LinkedHashMap&lt;K, V&gt; {
    private int capacity;

    /**
     * 传递进来最多能缓存多少数据
     *
     * @param capacity 缓存大小
     */
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    /**
     * 如果 map 中的数据量大于设定的最大容量，返回 true，在新加入对象时删除最老的数据
     *
     * @param eldest 最老的数据项
     * @return true 则移除最老的数据
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry&lt;K, V&gt; eldest) {
        // 当 map 中的数据量大于指定的缓存个数的时候，自动移除最老的数据
        return size() &gt; capacity;
    }
}`, `29521480092770890000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LRUCache&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkedHashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; capacity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 传递进来最多能缓存多少数据
     *
     * @param capacity 缓存大小
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LRUCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; capacity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;capacity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.75f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;capacity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; capacity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 如果 map 中的数据量大于设定的最大容量，返回 true，在新加入对象时删除最老的数据
     *
     * @param eldest 最老的数据项
     * @return true 则移除最老的数据
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeEldestEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; eldest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 当 map 中的数据量大于指定的缓存个数的时候，自动移除最老的数据&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; capacity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;如何保证-redis-的高并发和高可用？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81-redis-%E7%9A%84%E9%AB%98%E5%B9%B6%E5%8F%91%E5%92%8C%E9%AB%98%E5%8F%AF%E7%94%A8%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证 redis 的高并发和高可用？&lt;/h3&gt;
&lt;p&gt;redis 实现高并发主要依靠主从架构，一主多从，一般来说，很多项目其实就足够了，单主用来写入数据，单机几万 QPS，多从用来查询数据，多个从实例可以提供每秒 10w 的 QPS。&lt;/p&gt;
&lt;p&gt;如果想要在实现高并发的同时，容纳大量的数据，那么就需要 redis 集群，使用 redis 集群之后，可以提供每秒几十万的读写并发。&lt;/p&gt;
&lt;p&gt;redis 高可用，如果是做主从架构部署，那么加上哨兵就可以了，就可以实现任何一个实例宕机就进行主备切换。&lt;/p&gt;
&lt;h3 id=&quot;redis-主从架构是怎样的？&quot;&gt;&lt;a href=&quot;#redis-%E4%B8%BB%E4%BB%8E%E6%9E%B6%E6%9E%84%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 主从架构是怎样的？&lt;/h3&gt;
&lt;p&gt;单机的 Redis，能够承载的 QPS 大概就在上万到几万不等。对于缓存来说，一般都是用来支撑读高并发的。因此架构做成主从（master-slave）架构，一主多从，主负责写，并且将数据复制到其它的 slave 节点，从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容，支撑读高并发。&lt;/p&gt;
&lt;p&gt;Redis replication -&gt; 主从架构 -&gt; 读写分离 -&gt; 水平扩容支撑读高并发&lt;/p&gt;
&lt;h4 id=&quot;redis-replication-的核心机制&quot;&gt;&lt;a href=&quot;#redis-replication-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis replication 的核心机制&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Redis 采用异步方式复制数据到 slave 节点，不过 Redis2.8 开始，slave node 会周期性地确认自己每次复制的数据量；&lt;/li&gt;
&lt;li&gt;一个 master node 是可以配置多个 slave node 的；&lt;/li&gt;
&lt;li&gt;slave node 也可以连接其他的 slave node；&lt;/li&gt;
&lt;li&gt;slave node 做复制的时候，不会 block master node 的正常工作；&lt;/li&gt;
&lt;li&gt;slave node 在做复制的时候，也不会 block 对自己的查询操作，它会用旧的数据集来提供服务；但是复制完成的时候，需要删除旧数据集，加载新数据集，这个时候就会暂停对外服务了；&lt;/li&gt;
&lt;li&gt;slave node 主要用来进行横向扩容，做读写分离，扩容的 slave node 可以提高读的吞吐量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意，如果采用了主从架构，那么建议必须开启 master node 的持久化，不建议用 slave node 作为 master node 的数据热备，因为那样的话，如果你关掉 master 的持久化，可能在 master 宕机重启的时候数据是空的，然后可能一经过复制，slave node 的数据也丢了。&lt;/p&gt;
&lt;p&gt;另外，master 的各种备份方案，也需要做。万一本地的所有文件丢失了，从备份中挑选一份 rdb 去恢复 master，这样才能确保启动的时候，是有数据的，即使采用了后续讲解的高可用机制，slave node 可以自动接管 master node，但也可能 sentinel 还没检测到 master failure，master node 就自动重启了，还是可能导致上面所有的 slave node 数据被清空。&lt;/p&gt;
&lt;h4 id=&quot;redis-主从复制的核心原理&quot;&gt;&lt;a href=&quot;#redis-%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E7%9A%84%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 主从复制的核心原理&lt;/h4&gt;
&lt;p&gt;当启动一个 slave node 的时候，它会发送一个 PSYNC 命令给 master node。&lt;/p&gt;
&lt;p&gt;如果这是 slave node 初次连接到 master node，那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程，开始生成一份 RDB 快照文件，同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后，master 会将这个 RDB 发送给 slave，slave 会先写入本地磁盘，然后再从本地磁盘加载到内存中，接着 master 会将内存中缓存的写命令发送到 slave，slave 也会同步这些数据。slave node 如果跟 master node 有网络故障，断开了连接，会自动重连，连接之后 master node 仅会复制给 slave 部分缺少的数据。&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;flowchart LR
    RDB --&gt; slave
    master --&gt; RDB
    master --&gt; |&quot;初次连接：全量复制&quot;| slave
    master --&gt; |&quot;重新连接：部分数据复制&quot;| slave
    slave --&gt; |ping| master
    slave --&gt; |写入| 磁盘[&quot;磁盘（RDB持久化）&quot;]
    磁盘 --&gt; |加载到内存| slave&lt;/div&gt;
&lt;h5 id=&quot;主从复制的断点续传&quot;&gt;&lt;a href=&quot;#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E7%9A%84%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;主从复制的断点续传&lt;/h5&gt;
&lt;p&gt;从 Redis2.8 开始，就支持主从复制的断点续传，如果主从复制过程中，网络连接断掉了，那么可以接着上次复制的地方，继续复制下去，而不是从头开始复制一份。&lt;/p&gt;
&lt;p&gt;master node 会在内存中维护一个 backlog，master 和 slave 都会保存一个 replica offset 还有一个 master run id，offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了，slave 会让 master 从上次 replica offset 开始继续复制，如果没有找到对应的 offset，那么就会执行一次 resynchronization。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果根据 host + ip 定位 master node，是不靠谱的，如果 master node 重启或者数据出现了变化，那么 slave node 应该根据不同的 run id 区分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id=&quot;无磁盘化复制&quot;&gt;&lt;a href=&quot;#%E6%97%A0%E7%A3%81%E7%9B%98%E5%8C%96%E5%A4%8D%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;无磁盘化复制&lt;/h5&gt;
&lt;p&gt;master 在内存中直接创建 RDB，然后发送给 slave，不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22468949299023254000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`repl-diskless-sync yes

# 等待 5s 后再开始复制，因为要等更多 slave 重新连接过来
repl-diskless-sync-delay 5`, `22468949299023254000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;repl-diskless-sync &lt;span class=&quot;token function&quot;&gt;yes&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 等待 5s 后再开始复制，因为要等更多 slave 重新连接过来&lt;/span&gt;
repl-diskless-sync-delay &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;过期-key-处理&quot;&gt;&lt;a href=&quot;#%E8%BF%87%E6%9C%9F-key-%E5%A4%84%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;过期 key 处理&lt;/h5&gt;
&lt;p&gt;slave 不会过期 key，只会等待 master 过期 key。如果 master 过期了一个 key，或者通过 LRU 淘汰了一个 key，那么会模拟一条 del 命令发送给 slave。&lt;/p&gt;
&lt;h4 id=&quot;复制的完整流程&quot;&gt;&lt;a href=&quot;#%E5%A4%8D%E5%88%B6%E7%9A%84%E5%AE%8C%E6%95%B4%E6%B5%81%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;复制的完整流程&lt;/h4&gt;
&lt;p&gt;slave node 启动时，会在自己本地保存 master node 的信息，包括 master node 的 host 和 ip，但是复制流程没开始。&lt;/p&gt;
&lt;p&gt;slave node 内部有个定时任务，每秒检查是否有新的 master node 要连接和复制，如果发现，就跟 master node 建立 socket 网络连接。然后 slave node 发送 ping 命令给 master node。如果 master 设置了 requirepass，那么 slave node 必须发送 masterauth 的口令过去进行认证。master node 第一次执行全量复制，将所有数据发给 slave node。而在后续，master node 持续将写命令，异步复制给 slave node。&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;flowchart LR
    client --&gt; master[&quot;master\n5. 每次 master 接收到新的数据，都异步发送给 slave node&quot;]
    master -- &quot;4. master 启动全量复制，\n将自己的所有数据都发送给 slave，\n实现数据的同步&quot; --&gt; slave
    slave -- &quot;socket 网络连接\n3. 如果 master 配置了 require pass，\n那么 slave node 会发送 master auth 口令去认证&quot; --&gt; master
    slave[&quot;slave\n1. slave 启动，在本地保存 master node 的 host 和 ip\n2. slave 内部有定时任务，每秒会 check 是否有 master 要连接，\n如果有就跟 master 建立 socket 网络来连接&quot;]&lt;/div&gt;
&lt;h5 id=&quot;全量复制&quot;&gt;&lt;a href=&quot;#%E5%85%A8%E9%87%8F%E5%A4%8D%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;全量复制&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;master 执行 bgsave，在本地生成一份 rdb 快照文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;master node 将 rdb 快照文件发送给 slave node，如果 rdb 复制时间超过 60 秒（repl-timeout），那么 slave node 就会认为复制失败，可以适当调大这个参数（对于千兆网卡的机器，一般每秒传输 100MB，6G 文件，很可能超过 60s）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;master node 在生成 rdb 时，会将所有新的写命令缓存在内存中，在 slave node 保存了 rdb 之后，再将新的写命令复制给 slave node。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果在复制期间，内存缓冲区持续消耗超过 64MB，或者一次性超过 256MB，那么停止复制，复制失败。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;71619010106471410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`client-output-buffer-limit slave 256MB 64MB 60`, `71619010106471410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;client-output-buffer-limit slave 256MB 64MB &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;slave node 接收到 rdb 之后，清空自己的旧数据，然后重新加载 rdb 到自己的内存中。注意，在清空旧数据之前，slave node 依然会基于旧的数据版本对外提供服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果 slave node 开启了 AOF，那么会立即执行 BGREWRITEAOF，重写 AOF。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;增量复制&quot;&gt;&lt;a href=&quot;#%E5%A2%9E%E9%87%8F%E5%A4%8D%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;增量复制&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;如果全量复制过程中，master-slave 网络连接断掉，那么 slave 重新连接 master 时，会触发增量复制。&lt;/li&gt;
&lt;li&gt;master 直接从自己的 backlog 中获取部分丢失的数据，发送给 slave node，默认 backlog 就是 1MB。&lt;/li&gt;
&lt;li&gt;master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;heartbeat&quot;&gt;&lt;a href=&quot;#heartbeat&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;heartbeat&lt;/h5&gt;
&lt;p&gt;主从节点互相都会发送 heartbeat 信息。&lt;/p&gt;
&lt;p&gt;master 默认每隔 10 秒发送一次 heartbeat，slave node 每隔 1 秒发送一个 heartbeat。&lt;/p&gt;
&lt;h5 id=&quot;异步复制&quot;&gt;&lt;a href=&quot;#%E5%BC%82%E6%AD%A5%E5%A4%8D%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;异步复制&lt;/h5&gt;
&lt;p&gt;master 每次接收到写命令之后，先在内部写入数据，然后异步发送给 slave node。&lt;/p&gt;
&lt;h4 id=&quot;redis-如何才能做到高可用&quot;&gt;&lt;a href=&quot;#redis-%E5%A6%82%E4%BD%95%E6%89%8D%E8%83%BD%E5%81%9A%E5%88%B0%E9%AB%98%E5%8F%AF%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 如何才能做到高可用&lt;/h4&gt;
&lt;p&gt;如果系统在 365 天内，有 99.99% 的时间，都是可以对外提供服务的，那么就说系统是高可用的。&lt;/p&gt;
&lt;p&gt;一个 slave 挂掉了，是不会影响可用性的，还有其它的 slave 在提供相同数据下的相同的对外的查询服务。&lt;/p&gt;
&lt;p&gt;但是，如果 master node 死掉了，会怎么样？没法写数据了，写缓存的时候，全部失效了。slave node 还有什么用呢，没有 master 给它们复制数据了，系统相当于不可用了。&lt;/p&gt;
&lt;p&gt;Redis 的高可用架构，叫做 failover 故障转移，也可以叫做主备切换。&lt;/p&gt;
&lt;p&gt;master node 在故障时自动检测，并且将某个 slave node 自动切换为 master node 的过程，叫做主备切换。这个过程，实现了 Redis 的主从架构下的高可用。&lt;/p&gt;
&lt;h3 id=&quot;redis-的持久化有哪几种方式？&quot;&gt;&lt;a href=&quot;#redis-%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 的持久化有哪几种方式？&lt;/h3&gt;
&lt;p&gt;持久化主要是做灾难恢复、数据恢复，也可以归类到高可用的一个环节中去，比如 Redis 整个挂了，然后 Redis 就不可用了，你要做的事情就是让 Redis 变得可用，尽快变得可用。&lt;/p&gt;
&lt;p&gt;重启 Redis，尽快让它对外提供服务，如果没做数据备份，这时候 Redis 启动了，也不可用啊，数据都没了。&lt;/p&gt;
&lt;p&gt;很可能说，大量的请求过来，缓存全部无法命中，在 Redis 里根本找不到数据，这个时候就死定了，出现缓存雪崩问题。所有请求没有在 Redis 命中，就会去 mysql 数据库这种数据源头中去找，一下子 mysql 承接高并发，然后就挂了。&lt;/p&gt;
&lt;p&gt;如果你把 Redis 持久化做好，备份和恢复方案做到企业级的程度，那么即使你的 Redis 故障了，也可以通过备份数据，快速恢复，一旦恢复立即对外提供服务。&lt;/p&gt;
&lt;h4 id=&quot;redis-持久化的两种方式&quot;&gt;&lt;a href=&quot;#redis-%E6%8C%81%E4%B9%85%E5%8C%96%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 持久化的两种方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;RDB：RDB 持久化机制，是对 Redis 中的数据执行周期性的持久化。&lt;/li&gt;
&lt;li&gt;AOF：AOF 机制对每条写入命令作为日志，以 append-only 的模式写入一个日志文件中，在 Redis 重启的时候，可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过 RDB 或 AOF，都可以将 Redis 内存中的数据给持久化到磁盘上面来，然后可以将这些数据备份到别的地方去，比如说阿里云等云服务。&lt;/p&gt;
&lt;p&gt;如果 Redis 挂了，服务器上的内存和磁盘上的数据都丢了，可以从云服务上拷贝回来之前的数据，放到指定的目录中，然后重新启动 Redis，Redis 就会自动根据持久化数据文件中的数据，去恢复内存中的数据，继续对外提供服务。&lt;/p&gt;
&lt;p&gt;如果同时使用 RDB 和 AOF 两种持久化机制，那么在 Redis 重启的时候，会使用 AOF 来重新构建数据，因为 AOF 中的数据更加完整。&lt;/p&gt;
&lt;h5 id=&quot;rdb-优缺点&quot;&gt;&lt;a href=&quot;#rdb-%E4%BC%98%E7%BC%BA%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RDB 优缺点&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;RDB 会生成多个数据文件，每个数据文件都代表了某一个时刻中 Redis 的数据，这种多个数据文件的方式，非常适合做冷备，可以将这种完整的数据文件发送到一些远程的安全存储上去，比如说 Amazon 的 S3 云服务上去，在国内可以是阿里云的 ODPS 分布式存储上，以预定好的备份策略来定期备份 Redis 中的数据。&lt;/li&gt;
&lt;li&gt;RDB 对 Redis 对外提供的读写服务，影响非常小，可以让 Redis 保持高性能，因为 Redis 主进程只需要 fork 一个子进程，让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。&lt;/li&gt;
&lt;li&gt;相对于 AOF 持久化机制来说，直接基于 RDB 数据文件来重启和恢复 Redis 进程，更加快速。&lt;/li&gt;
&lt;li&gt;如果想要在 Redis 故障时，尽可能少的丢失数据，那么 RDB 没有 AOF 好。一般来说，RDB 数据快照文件，都是每隔 5 分钟，或者更长时间生成一次，这个时候就得接受一旦 Redis 进程宕机，那么会丢失最近 5 分钟（甚至更长时间）的数据。&lt;/li&gt;
&lt;li&gt;RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候，如果数据文件特别大，可能会导致对客户端提供的服务暂停数毫秒，或者甚至数秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;aof-优缺点&quot;&gt;&lt;a href=&quot;#aof-%E4%BC%98%E7%BC%BA%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;AOF 优缺点&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;AOF 可以更好的保护数据不丢失，一般 AOF 会每隔 1 秒，通过一个后台线程执行一次 fsync 操作，最多丢失 1 秒钟的数据。&lt;/li&gt;
&lt;li&gt;AOF 日志文件以 append-only 模式写入，所以没有任何磁盘寻址的开销，写入性能非常高，而且文件不容易破损，即使文件尾部破损，也很容易修复。&lt;/li&gt;
&lt;li&gt;AOF 日志文件即使过大的时候，出现后台重写操作，也不会影响客户端的读写。因为在 rewrite log 的时候，会对其中的指令进行压缩，创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候，老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候，再交换新老日志文件即可。&lt;/li&gt;
&lt;li&gt;AOF 日志文件的命令通过可读较强的方式进行记录，这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据，只要这个时候后台 rewrite 还没有发生，那么就可以立即拷贝 AOF 文件，将最后一条 flushall 命令给删了，然后再将该 AOF 文件放回去，就可以通过恢复机制，自动恢复所有数据。&lt;/li&gt;
&lt;li&gt;对于同一份数据来说，AOF 日志文件通常比 RDB 数据快照文件更大。&lt;/li&gt;
&lt;li&gt;AOF 开启后，支持的写 QPS 会比 RDB 支持的写 QPS 低，因为 AOF 一般会配置成每秒 fsync 一次日志文件，当然，每秒一次 fsync，性能也还是很高的（如果实时写入，那么 QPS 会大降，Redis 性能会大大降低）&lt;/li&gt;
&lt;li&gt;以前 AOF 发生过 bug，就是通过 AOF 记录的日志，进行数据恢复的时候，没有恢复一模一样的数据出来。所以说，类似 AOF 这种较为复杂的基于命令日志 merge 回放的方式，比基于 RDB 每次持久化一份完整的数据快照文件的方式，更加脆弱一些，容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug，因此每次 rewrite 并不是基于旧的指令日志进行 merge 的，而是基于当时内存中的数据进行指令的重新构建，这样健壮性会好很多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;rdb-和-aof-到底该如何选择&quot;&gt;&lt;a href=&quot;#rdb-%E5%92%8C-aof-%E5%88%B0%E5%BA%95%E8%AF%A5%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RDB 和 AOF 到底该如何选择&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;不要仅仅使用 RDB，因为那样会导致你丢失很多数据；&lt;/li&gt;
&lt;li&gt;也不要仅仅使用 AOF，因为那样有两个问题：第一，你通过 AOF 做冷备，没有 RDB 做冷备来的恢复速度更快；第二，RDB 每次简单粗暴生成数据快照，更加健壮，可以避免 AOF 这种复杂的备份和恢复机制的 bug；&lt;/li&gt;
&lt;li&gt;Redis 支持同时开启两种持久化方式，我们可以综合使用 AOF 和 RDB 两种持久化机制，用 AOF 来保证数据不丢失，作为数据恢复的第一选择；用 RDB 来做不同程度的冷备，在 AOF 文件都丢失或损坏不可用的时候，还可以使用 RDB 来进行快速的数据恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;redis-哨兵集群如何实现高可用？&quot;&gt;&lt;a href=&quot;#redis-%E5%93%A8%E5%85%B5%E9%9B%86%E7%BE%A4%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E9%AB%98%E5%8F%AF%E7%94%A8%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 哨兵集群如何实现高可用？&lt;/h3&gt;
&lt;h4 id=&quot;哨兵的介绍&quot;&gt;&lt;a href=&quot;#%E5%93%A8%E5%85%B5%E7%9A%84%E4%BB%8B%E7%BB%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;哨兵的介绍&lt;/h4&gt;
&lt;p&gt;sentinel，中文名是哨兵。哨兵是 Redis 集群架构中非常重要的一个组件，主要有以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集群监控：负责监控 Redis master 和 slave 进程是否正常工作。&lt;/li&gt;
&lt;li&gt;消息通知：如果某个 Redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。&lt;/li&gt;
&lt;li&gt;故障转移：如果 master node 挂掉了，会自动转移到 slave node 上。&lt;/li&gt;
&lt;li&gt;配置中心：如果故障转移发生了，通知 client 客户端新的 master 地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哨兵用于实现 Redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举的问题。&lt;/li&gt;
&lt;li&gt;即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的，因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的，那就很坑爹了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;哨兵的核心知识&quot;&gt;&lt;a href=&quot;#%E5%93%A8%E5%85%B5%E7%9A%84%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;哨兵的核心知识&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;哨兵至少需要 3 个实例，来保证自己的健壮性。&lt;/li&gt;
&lt;li&gt;哨兵 + Redis 主从的部署架构，是不保证数据零丢失的，只能保证 Redis 集群的高可用性。&lt;/li&gt;
&lt;li&gt;对于哨兵 + Redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哨兵集群必须部署 2 个以上节点，如果哨兵集群仅仅部署了 2 个哨兵实例，quorum = 1。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;94291953887045000000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+`, `94291953887045000000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;配置 quorum = 1，如果 master 宕机，s1 和 s2 中只要有 1 个哨兵认为 master 宕机了，就可以进行切换，同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候，需要 majority，也就是大多数哨兵都是运行的。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;14066637663095660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`2 个哨兵，majority = 2
3 个哨兵，majority = 2
4 个哨兵，majority = 2
5 个哨兵，majority = 3
...`, `14066637663095660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2 个哨兵，majority = 2
3 个哨兵，majority = 2
4 个哨兵，majority = 2
5 个哨兵，majority = 3
...&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果此时仅仅是 M1 进程宕机了，哨兵 s1 正常运行，那么故障转移是 OK 的。但是如果是整个 M1 和 S1 运行的机器宕机了，那么哨兵只有 1 个，此时就没有 majority 来允许执行故障转移，虽然另外一台机器上还有一个 R1，但是故障转移不会执行。&lt;/p&gt;
&lt;p&gt;经典的 3 节点哨兵集群是这样的：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;60464782211574160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`       +----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+`, `60464782211574160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;       +----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;配置 quorum = 2，如果 M1 所在机器宕机了，那么三个哨兵还剩下 2 个，S2 和 S3 可以一致认为 master 宕机了，然后选举出一个来执行故障转移，同时 3 个哨兵的 majority 是 2，所以还剩下的 2 个哨兵运行着，就可以允许执行故障转移。&lt;/p&gt;
&lt;h4 id=&quot;redis-哨兵主备切换的数据丢失问题&quot;&gt;&lt;a href=&quot;#redis-%E5%93%A8%E5%85%B5%E4%B8%BB%E5%A4%87%E5%88%87%E6%8D%A2%E7%9A%84%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 哨兵主备切换的数据丢失问题&lt;/h4&gt;
&lt;h5 id=&quot;导致数据丢失的两种情况&quot;&gt;&lt;a href=&quot;#%E5%AF%BC%E8%87%B4%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%83%85%E5%86%B5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;导致数据丢失的两种情况&lt;/h5&gt;
&lt;p&gt;主备切换的过程，可能会导致数据丢失：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;异步复制导致的数据丢失&lt;/p&gt;
&lt;p&gt;因为 master -&gt; slave 的复制是异步的，所以可能有部分数据还没复制到 slave，master 就宕机了，此时这部分数据就丢失了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;脑裂导致的数据丢失&lt;/p&gt;
&lt;p&gt;脑裂，也就是说，某个 master 所在机器突然脱离了正常的网络，跟其他 slave 机器不能连接，但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了，然后开启选举，将其他 slave 切换成了 master。这个时候，集群里就会有两个 master，也就是所谓的脑裂。&lt;/p&gt;
&lt;p&gt;此时虽然某个 slave 被切换成了 master，但是可能 client 还没来得及切换到新的 master，还继续向旧 master 写数据。因此旧 master 再次恢复的时候，会被作为一个 slave 挂到新的 master 上去，自己的数据会清空，重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据，因此，这部分数据也就丢失了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;数据丢失问题的解决方案&quot;&gt;&lt;a href=&quot;#%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;数据丢失问题的解决方案&lt;/h5&gt;
&lt;p&gt;进行如下配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17087415333811261000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`min-slaves-to-write 1
min-slaves-max-lag 10`, `17087415333811261000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;min-slaves-to-write &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
min-slaves-max-lag &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;表示，要求至少有 1 个 slave，数据复制和同步的延迟不能超过 10 秒。&lt;/p&gt;
&lt;p&gt;如果说一旦所有的 slave，数据复制和同步的延迟都超过了 10 秒钟，那么这个时候，master 就不会再接收任何请求了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;减少异步复制数据的丢失&lt;/p&gt;
&lt;p&gt;有了 min-slaves-max-lag 这个配置，就可以确保说，一旦 slave 复制数据和 ack 延时太长，就认为可能 master 宕机后损失的数据太多了，那么就拒绝写请求，这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;减少脑裂的数据丢失&lt;/p&gt;
&lt;p&gt;如果一个 master 出现了脑裂，跟其他 slave 丢了连接，那么上面两个配置可以确保说，如果不能继续给指定数量的 slave 发送数据，而且 slave 超过 10 秒没有给自己 ack 消息，那么就直接拒绝客户端的写请求。因此在脑裂场景下，最多就丢失 10 秒的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sdown-和-odown-转换机制&quot;&gt;&lt;a href=&quot;#sdown-%E5%92%8C-odown-%E8%BD%AC%E6%8D%A2%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;sdown 和 odown 转换机制&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;sdown 是主观宕机，就一个哨兵如果自己觉得一个 master 宕机了，那么就是主观宕机&lt;/li&gt;
&lt;li&gt;odown 是客观宕机，如果 quorum 数量的哨兵都觉得一个 master 宕机了，那么就是客观宕机&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;sdown 达成的条件很简单，如果一个哨兵 ping 一个 master，超过了 is-master-down-after-milliseconds 指定的毫秒数之后，就主观认为 master 宕机了；如果一个哨兵在指定时间内，收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的，那么就认为是 odown 了。&lt;/p&gt;
&lt;h4 id=&quot;哨兵集群的自动发现机制&quot;&gt;&lt;a href=&quot;#%E5%93%A8%E5%85%B5%E9%9B%86%E7%BE%A4%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8F%91%E7%8E%B0%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;哨兵集群的自动发现机制&lt;/h4&gt;
&lt;p&gt;哨兵互相之间的发现，是通过 Redis 的 pub/sub 系统实现的，每个哨兵都会往 &lt;code class=&quot;language-text&quot;&gt;__sentinel__:hello&lt;/code&gt; 这个 channel 里发送一个消息，这时候所有其他哨兵都可以消费到这个消息，并感知到其他的哨兵的存在。&lt;/p&gt;
&lt;p&gt;每隔两秒钟，每个哨兵都会往自己监控的某个 master + slaves 对应的 &lt;code class=&quot;language-text&quot;&gt;__sentinel__:hello&lt;/code&gt; channel 里发送一个消息，内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。&lt;/p&gt;
&lt;p&gt;每个哨兵也会去监听自己监控的每个 master + slaves 对应的 &lt;code class=&quot;language-text&quot;&gt;__sentinel__:hello&lt;/code&gt; channel，然后去感知到同样在监听这个 master + slaves 的其他哨兵的存在。&lt;/p&gt;
&lt;p&gt;每个哨兵还会跟其他哨兵交换对 master 的监控配置，互相进行监控配置的同步。&lt;/p&gt;
&lt;h4 id=&quot;slave-配置的自动纠正&quot;&gt;&lt;a href=&quot;#slave-%E9%85%8D%E7%BD%AE%E7%9A%84%E8%87%AA%E5%8A%A8%E7%BA%A0%E6%AD%A3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;slave 配置的自动纠正&lt;/h4&gt;
&lt;p&gt;哨兵会负责自动纠正 slave 的一些配置，比如 slave 如果要成为潜在的 master 候选人，哨兵会确保 slave 复制现有 master 的数据；如果 slave 连接到了一个错误的 master 上，比如故障转移之后，那么哨兵会确保它们连接到正确的 master 上。&lt;/p&gt;
&lt;h4 id=&quot;slave---master-选举算法&quot;&gt;&lt;a href=&quot;#slave---master-%E9%80%89%E4%B8%BE%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;slave -&gt; master 选举算法&lt;/h4&gt;
&lt;p&gt;如果一个 master 被认为 odown 了，而且 majority 数量的哨兵都允许主备切换，那么某个哨兵就会执行主备切换操作，此时首先要选举一个 slave 来，会考虑 slave 的一些信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跟 master 断开连接的时长&lt;/li&gt;
&lt;li&gt;slave 优先级&lt;/li&gt;
&lt;li&gt;复制 offset&lt;/li&gt;
&lt;li&gt;run id&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果一个 slave 跟 master 断开连接的时间已经超过了 down-after-milliseconds 的 10 倍，外加 master 宕机的时长，那么 slave 就被认为不适合选举为 master。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53906782350037720000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state`, `53906782350037720000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来会对 slave 进行排序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按照 slave 优先级进行排序，slave priority 越低，优先级就越高。&lt;/li&gt;
&lt;li&gt;如果 slave priority 相同，那么看 replica offset，哪个 slave 复制了越多的数据，offset 越靠后，优先级就越高。&lt;/li&gt;
&lt;li&gt;如果上面两个条件都相同，那么选择一个 run id 比较小的那个 slave。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;quorum-和-majority&quot;&gt;&lt;a href=&quot;#quorum-%E5%92%8C-majority&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;quorum 和 majority&lt;/h4&gt;
&lt;p&gt;每次一个哨兵要做主备切换，首先需要 quorum 数量的哨兵认为 odown，然后选举出一个哨兵来做切换，这个哨兵还需要得到 majority 哨兵的授权，才能正式执行切换。&lt;/p&gt;
&lt;p&gt;如果 quorum &amp;#x3C; majority，比如 5 个哨兵，majority 就是 3，quorum 设置为 2，那么就 3 个哨兵授权就可以执行切换。&lt;/p&gt;
&lt;p&gt;但是如果 quorum &gt;= majority，那么必须 quorum 数量的哨兵都授权，比如 5 个哨兵，quorum 是 5，那么必须 5 个哨兵都同意授权，才能执行切换。&lt;/p&gt;
&lt;h4 id=&quot;configuration-epoch&quot;&gt;&lt;a href=&quot;#configuration-epoch&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;configuration epoch&lt;/h4&gt;
&lt;p&gt;哨兵会对一套 Redis master + slaves 进行监控，有相应的监控的配置。&lt;/p&gt;
&lt;p&gt;执行切换的那个哨兵，会从要切换到的新 master（salve -&gt; master）那里得到一个 configuration epoch，这就是一个 version 号，每次切换的 version 号都必须是唯一的。&lt;/p&gt;
&lt;p&gt;如果第一个选举出的哨兵切换失败了，那么其他哨兵，会等待 failover-timeout 时间，然后接替继续执行切换，此时会重新获取一个新的 configuration epoch，作为新的 version 号。&lt;/p&gt;
&lt;h4 id=&quot;configuration-传播&quot;&gt;&lt;a href=&quot;#configuration-%E4%BC%A0%E6%92%AD&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;configuration 传播&lt;/h4&gt;
&lt;p&gt;哨兵完成切换之后，会在自己本地更新生成最新的 master 配置，然后同步给其他的哨兵，就是通过之前说的 pub/sub 消息机制。&lt;/p&gt;
&lt;p&gt;这里之前的 version 号就很重要了，因为各种消息都是通过一个 channel 去发布和监听的，所以一个哨兵完成一次新的切换之后，新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。&lt;/p&gt;
&lt;h3 id=&quot;redis-集群模式的工作原理能说一下么？&quot;&gt;&lt;a href=&quot;#redis-%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%83%BD%E8%AF%B4%E4%B8%80%E4%B8%8B%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 集群模式的工作原理能说一下么？&lt;/h3&gt;
&lt;p&gt;在前几年，Redis 如果要搞几个节点，每个节点存储一部分的数据，得借助一些中间件来实现，比如说有 codis，或者 twemproxy，都有。有一些 Redis 中间件，你读写 Redis 中间件，Redis 中间件负责将你的数据分布式存储在多台机器上的 Redis 实例中。&lt;/p&gt;
&lt;p&gt;这两年，Redis 不断在发展，Redis 也不断有新的版本，现在的 Redis 集群模式，可以做到在多台机器上，部署多个 Redis 实例，每个实例存储一部分的数据，同时每个 Redis 主实例可以挂 Redis 从实例，自动确保说，如果 Redis 主实例挂了，会自动切换到 Redis 从实例上来。&lt;/p&gt;
&lt;p&gt;现在 Redis 的新版本，大家都是用 Redis cluster 的，也就是 Redis 原生支持的 Redis 集群模式，那么面试官肯定会就 Redis cluster 对你来个几连炮。要是你没用过 Redis cluster，正常，以前很多人用 codis 之类的客户端来支持集群，但是起码你得研究一下 Redis cluster 吧。&lt;/p&gt;
&lt;p&gt;如果你的数据量很少，主要是承载高并发高性能的场景，比如你的缓存一般就几个 G，单机就足够了，可以使用 replication，一个 master 多个 slaves，要几个 slave 跟你要求的读吞吐量有关，然后自己搭建一个 sentinel 集群去保证 Redis 主从架构的高可用性。&lt;/p&gt;
&lt;p&gt;Redis cluster，主要是针对海量数据 + 高并发 + 高可用的场景。Redis cluster 支撑 N 个 Redis master node，每个 master node 都可以挂载多个 slave node。这样整个 Redis 就可以横向扩容了。如果你要支撑更大数据量的缓存，那就横向扩容更多的 master 节点，每个 master 节点就能存放更多的数据了。&lt;/p&gt;
&lt;h4 id=&quot;redis-cluster-介绍&quot;&gt;&lt;a href=&quot;#redis-cluster-%E4%BB%8B%E7%BB%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis cluster 介绍&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;自动将数据进行分片，每个 master 上放一部分数据&lt;/li&gt;
&lt;li&gt;提供内置的高可用支持，部分 master 不可用时，还是可以继续工作的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 Redis cluster 架构下，每个 Redis 要放开两个端口号，比如一个是 6379，另外一个就是加 1w 的端口号，比如 16379。&lt;/p&gt;
&lt;p&gt;16379 端口号是用来进行节点间通信的，也就是 cluster bus 的东西，cluster bus 的通信，用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议，gossip 协议，用于节点间进行高效的数据交换，占用更少的网络带宽和处理时间。&lt;/p&gt;
&lt;h4 id=&quot;节点间的内部通信机制&quot;&gt;&lt;a href=&quot;#%E8%8A%82%E7%82%B9%E9%97%B4%E7%9A%84%E5%86%85%E9%83%A8%E9%80%9A%E4%BF%A1%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;节点间的内部通信机制&lt;/h4&gt;
&lt;h5 id=&quot;基本通信原理&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E6%9C%AC%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基本通信原理&lt;/h5&gt;
&lt;p&gt;集群元数据的维护有两种方式：集中式、Gossip 协议。Redis cluster 节点间采用 gossip 协议进行通信。&lt;/p&gt;
&lt;p&gt;集中式是将集群元数据（节点信息、故障等等）集中存储在某个节点上。集中式元数据集中存储的一个典型代表，就是大数据领域的 storm。它是分布式的大数据实时计算引擎，是集中式的元数据存储的结构，底层基于 zookeeper（分布式协调的中间件）对所有元数据进行存储维护。&lt;/p&gt;
&lt;p&gt;Redis 维护集群元数据采用另一个方式，gossip 协议，所有节点都持有一份元数据，不同的节点如果出现了元数据的变更，就不断将元数据发送给其它的节点，让其它节点也进行元数据的变更。&lt;/p&gt;
&lt;p&gt;集中式的好处在于，元数据的读取和更新，时效性非常好，一旦元数据出现了变更，就立即更新到集中式的存储中，其它节点读取的时候就可以感知到；不好在于，所有的元数据的更新压力全部集中在一个地方，可能会导致元数据的存储有压力。&lt;/p&gt;
&lt;p&gt;gossip 好处在于，元数据的更新比较分散，不是集中在一个地方，更新请求会陆陆续续打到所有节点上去更新，降低了压力；不好在于，元数据的更新有延时，可能导致集群中的一些操作会有一些滞后。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10000 端口：每个节点都有一个专门用于节点间通信的端口，就是自己提供服务的端口号 + 10000，比如 7001，那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 ping 消息，同时其它几个节点接收到 ping 之后返回 pong 。&lt;/li&gt;
&lt;li&gt;交换的信息：信息包括故障信息，节点的增加和删除，hash slot 信息等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;gossip-协议&quot;&gt;&lt;a href=&quot;#gossip-%E5%8D%8F%E8%AE%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;gossip 协议&lt;/h5&gt;
&lt;p&gt;gossip 协议包含多种消息，包含 ping, pong, meet, fail 等等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;meet：某个节点发送 meet 给新加入的节点，让新节点加入集群中，然后新节点就会开始与其它节点进行通信。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54604432826154190000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Redis-trib.rb add-node`, `54604432826154190000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Redis-trib.rb add-node&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其实内部就是发送了一个 gossip meet 消息给新加入的节点，通知那个节点去加入我们的集群。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ping：每个节点都会频繁给其它节点发送 ping，其中包含自己的状态还有自己维护的集群元数据，互相通过 ping 交换元数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pong：返回 ping 和 meet，包含自己的状态和其它信息，也用于信息广播和更新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fail：某个节点判断另一个节点 fail 之后，就发送 fail 给其它节点，通知其它节点说，某个节点宕机啦。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;ping-消息深入&quot;&gt;&lt;a href=&quot;#ping-%E6%B6%88%E6%81%AF%E6%B7%B1%E5%85%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ping 消息深入&lt;/h5&gt;
&lt;p&gt;ping 时要携带一些元数据，如果很频繁，可能会加重网络负担。&lt;/p&gt;
&lt;p&gt;每个节点每秒会执行 10 次 ping，每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 &lt;code class=&quot;language-text&quot;&gt;cluster_node_timeout / 2&lt;/code&gt;，那么立即发送 ping，避免数据交换延时过长，落后的时间太长了。比如说，两个节点之间都 10 分钟没有交换数据了，那么整个集群处于严重的元数据不一致的情况，就会有问题。所以 &lt;code class=&quot;language-text&quot;&gt;cluster_node_timeout&lt;/code&gt; 可以调节，如果调得比较大，那么会降低 ping 的频率。&lt;/p&gt;
&lt;p&gt;每次 ping，会带上自己节点的信息，还有就是带上 1 / 10 其它节点的信息，发送出去，进行交换。至少包含 3 个其它节点的信息，最多包含总节点数减 2 个其它节点的信息。&lt;/p&gt;
&lt;h4 id=&quot;分布式寻址算法&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E5%AF%BB%E5%9D%80%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式寻址算法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;hash 算法（大量缓存重建）&lt;/li&gt;
&lt;li&gt;一致性 hash 算法（自动缓存迁移）+ 虚拟节点（自动负载均衡）&lt;/li&gt;
&lt;li&gt;Redis cluster 的 hash slot 算法&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;hash-算法&quot;&gt;&lt;a href=&quot;#hash-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;hash 算法&lt;/h5&gt;
&lt;p&gt;来了一个 key，首先计算 hash 值，然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机，所有请求过来，都会基于最新的剩余 master 节点数去取模，尝试去取数据。这会导致大部分的请求过来，全部无法拿到有效的缓存，导致大量的流量涌入数据库。&lt;/p&gt;
&lt;h5 id=&quot;一致性-hash-算法&quot;&gt;&lt;a href=&quot;#%E4%B8%80%E8%87%B4%E6%80%A7-hash-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;一致性 hash 算法&lt;/h5&gt;
&lt;p&gt;一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环，整个空间按顺时针方向组织，下一步将各个 master 节点（使用服务器的 ip 或主机名）进行 hash。这样就能确定每个节点在其哈希环上的位置。&lt;/p&gt;
&lt;p&gt;来了一个 key，首先计算 hash 值，并确定此数据在环上的位置，从此位置沿环顺时针“行走”，遇到的第一个 master 节点就是 key 所在位置。&lt;/p&gt;
&lt;p&gt;在一致性哈希算法中，如果一个节点挂了，受影响的数据仅仅是此节点到环空间前一个节点（沿着逆时针方向行走遇到的第一个节点）之间的数据，其它不受影响。增加一个节点也同理。&lt;/p&gt;
&lt;p&gt;但是，一致性哈希算法在节点太少时，容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题，一致性 hash 算法引入了虚拟节点机制，即对每一个节点计算多个 hash，每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布，负载均衡。&lt;/p&gt;
&lt;h5 id=&quot;redis-cluster-的-hash-slot-算法&quot;&gt;&lt;a href=&quot;#redis-cluster-%E7%9A%84-hash-slot-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis cluster 的 hash slot 算法&lt;/h5&gt;
&lt;p&gt;Redis cluster 有固定的 16384 个 hash slot，对每个 key 计算 CRC16 值，然后对 16384 取模，可以获取 key 对应的 hash slot。&lt;/p&gt;
&lt;p&gt;Redis cluster 中每个 master 都会持有部分 slot，比如有 3 个 master，那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单，增加一个 master，就将其他 master 的 hash slot 移动部分过去，减少一个 master，就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api，可以对指定的数据，让他们走同一个 hash slot，通过 hash tag 来实现。&lt;/p&gt;
&lt;p&gt;任何一台机器宕机，另外两个节点，不影响的。因为 key 找的是 hash slot，不是机器。&lt;/p&gt;
&lt;h4 id=&quot;redis-cluster-的高可用与主备切换原理&quot;&gt;&lt;a href=&quot;#redis-cluster-%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E4%B8%8E%E4%B8%BB%E5%A4%87%E5%88%87%E6%8D%A2%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis cluster 的高可用与主备切换原理&lt;/h4&gt;
&lt;p&gt;Redis cluster 的高可用的原理，几乎跟哨兵是类似的。&lt;/p&gt;
&lt;h5 id=&quot;判断节点宕机&quot;&gt;&lt;a href=&quot;#%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%E5%AE%95%E6%9C%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;判断节点宕机&lt;/h5&gt;
&lt;p&gt;如果一个节点认为另外一个节点宕机，那么就是 pfail，主观宕机。如果多个节点都认为另外一个节点宕机了，那么就是 fail，客观宕机，跟哨兵的原理几乎一样，sdown，odown。&lt;/p&gt;
&lt;p&gt;在 cluster-node-timeout 内，某个节点一直没有返回 pong，那么就被认为 pfail。&lt;/p&gt;
&lt;p&gt;如果一个节点认为某个节点 pfail 了，那么会在 gossip ping 消息中，ping 给其他节点，如果超过半数的节点都认为 pfail 了，那么就会变成 fail 。&lt;/p&gt;
&lt;h5 id=&quot;从节点过滤&quot;&gt;&lt;a href=&quot;#%E4%BB%8E%E8%8A%82%E7%82%B9%E8%BF%87%E6%BB%A4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;从节点过滤&lt;/h5&gt;
&lt;p&gt;对宕机的 master node，从其所有的 slave node 中，选择一个切换成 master node。&lt;/p&gt;
&lt;p&gt;检查每个 slave node 与 master node 断开连接的时间，如果超过了 &lt;code class=&quot;language-text&quot;&gt;cluster-node-timeout * cluster-slave-validity-factor&lt;/code&gt;，那么就没有资格切换成 master 。&lt;/p&gt;
&lt;h5 id=&quot;从节点选举&quot;&gt;&lt;a href=&quot;#%E4%BB%8E%E8%8A%82%E7%82%B9%E9%80%89%E4%B8%BE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;从节点选举&lt;/h5&gt;
&lt;p&gt;每个从节点，都根据自己对 master 复制数据的 offset，来设置一个选举时间，offset 越大（复制数据越多）的从节点，选举时间越靠前，优先进行选举。&lt;/p&gt;
&lt;p&gt;所有的 master node 开始 slave 选举投票，给要进行选举的 slave 进行投票，如果大部分 master node（N / 2 + 1）都投票给了某个从节点，那么选举通过，那个从节点可以切换成 master。&lt;/p&gt;
&lt;p&gt;从节点执行主备切换，从节点切换为主节点。&lt;/p&gt;
&lt;h5 id=&quot;与哨兵比较&quot;&gt;&lt;a href=&quot;#%E4%B8%8E%E5%93%A8%E5%85%B5%E6%AF%94%E8%BE%83&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;与哨兵比较&lt;/h5&gt;
&lt;p&gt;整个流程跟哨兵相比，非常类似，所以说，Redis cluster 功能强大，直接集成了 replication 和 sentinel 的功能。&lt;/p&gt;
&lt;h3 id=&quot;redis-的雪崩、穿透和击穿，如何应对？&quot;&gt;&lt;a href=&quot;#redis-%E7%9A%84%E9%9B%AA%E5%B4%A9%E3%80%81%E7%A9%BF%E9%80%8F%E5%92%8C%E5%87%BB%E7%A9%BF%EF%BC%8C%E5%A6%82%E4%BD%95%E5%BA%94%E5%AF%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 的雪崩、穿透和击穿，如何应对？&lt;/h3&gt;
&lt;h4 id=&quot;缓存雪崩cache-avalanche&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9cache-avalanche&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存雪崩(Cache Avalanche)&lt;/h4&gt;
&lt;p&gt;对于系统 A，假设每天高峰期每秒 5000 个请求，本来缓存在高峰期可以扛住每秒 4000 个请求，但是缓存机器意外发生了全盘宕机。缓存挂了，此时 1 秒 5000 个请求全部落数据库，数据库必然扛不住，它会报一下警，然后就挂了。此时，如果没有采用什么特别的方案来处理这个故障，DBA 很着急，重启数据库，但是数据库立马又被新的流量给打死了。&lt;/p&gt;
&lt;p&gt;这就是缓存雪崩。&lt;/p&gt;
&lt;p&gt;大约在 3 年前，国内比较知名的一个互联网公司，曾因为缓存事故，导致雪崩，后台系统全部崩溃，事故从当天下午持续到晚上凌晨 3~4 点，公司损失了几千万。&lt;/p&gt;
&lt;p&gt;缓存雪崩的事前事中事后的解决方案如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事前：Redis 高可用，主从 + 哨兵，Redis cluster，避免全盘崩溃。&lt;/li&gt;
&lt;li&gt;事中：本地 ehcache 缓存 + hystrix 限流 &amp;#x26; 降级，避免 MySQL 被打死。&lt;/li&gt;
&lt;li&gt;事后：Redis 持久化，一旦重启，自动从磁盘上加载数据，快速恢复缓存数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用户发送一个请求，系统 A 收到请求后，先查本地 ehcache 缓存，如果没查到再查 Redis。如果 ehcache 和 Redis 都没有，再查数据库，将数据库中的结果，写入 ehcache 和 Redis 中。&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;只要数据库不死，就是说，对用户来说，2/5 的请求都是可以被处理的。&lt;/li&gt;
&lt;li&gt;只要有 2/5 的请求可以被处理，就意味着你的系统没死，对用户来说，可能就是点击几次刷不出来页面，但是多点几次，就可以刷出来了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;缓存穿透cache-penetration&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8Fcache-penetration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存穿透(Cache Penetration)&lt;/h4&gt;
&lt;p&gt;对于系统 A，假设一秒 5000 个请求，结果其中 4000 个请求是黑客发出的恶意攻击。&lt;/p&gt;
&lt;p&gt;黑客发出的那 4000 个攻击，缓存中查不到，每次你去数据库里查，也查不到。&lt;/p&gt;
&lt;p&gt;举个栗子。数据库 id 是从 1 开始的，结果黑客发过来的请求 id 全部都是负数。这样的话，缓存中不会有，请求每次都“视缓存于无物”，直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。&lt;/p&gt;
&lt;p&gt;解决方式很简单，每次系统 A 从数据库中只要没查到，就写一个空值到缓存里去，比如 set -999 UNKNOWN。然后设置一个过期时间，这样的话，下次有相同的 key 来访问的时候，在缓存失效之前，都可以直接从缓存中取数据。&lt;/p&gt;
&lt;p&gt;当然，如果黑客如果每次使用不同的负数 id 来攻击，写空值的方法可能就不奏效了。更为常见的做法是在缓存之前增加布隆过滤器，将数据库中所有可能的数据哈希映射到布隆过滤器中。然后对每个请求进行如下判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请求数据的 key 不存在于布隆过滤器中，可以确定数据就一定不会存在于数据库中，系统可以立即返回不存在。&lt;/li&gt;
&lt;li&gt;请求数据的 key 存在于布隆过滤器中，则继续再向缓存中查询。使用布隆过滤器能够对访问的请求起到了一定的初筛作用，避免了因数据不存在引起的查询压力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;缓存击穿hotspot-invalid&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BFhotspot-invalid&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存击穿(Hotspot Invalid)&lt;/h4&gt;
&lt;p&gt;缓存击穿，就是说某个 key 非常热点，访问非常频繁，处于集中式高并发访问的情况，当这个 key 在失效的瞬间，大量的请求就击穿了缓存，直接请求数据库，就像是在一道屏障上凿开了一个洞。&lt;/p&gt;
&lt;p&gt;不同场景下的解决方式如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若缓存的数据是基本不会发生更新的，则可尝试将该热点数据设置为永不过期。&lt;/li&gt;
&lt;li&gt;若缓存的数据更新不频繁，且缓存刷新的整个流程耗时较少的情况下，则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁，或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存，其余线程则在锁释放后能访问到新缓存。&lt;/li&gt;
&lt;li&gt;若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下，可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间，以保证所有的请求能一直访问到对应的缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;如何保证缓存与数据库的双写一致性？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E7%BC%93%E5%AD%98%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何保证缓存与数据库的双写一致性？&lt;/h3&gt;
&lt;p&gt;一般来说，如果允许缓存可以稍微的跟数据库偶尔有不一致的情况，也就是说如果你的系统不是严格要求 “缓存 + 数据库” 必须保持一致性的话，最好不要做这个方案，即：读请求和写请求串行化，串到一个内存队列里去。&lt;/p&gt;
&lt;p&gt;串行化可以保证一定不会出现不一致的情况，但是它也会导致系统的吞吐量大幅度降低，用比正常情况下多几倍的机器去支撑线上的一个请求。&lt;/p&gt;
&lt;h4 id=&quot;cache-aside-pattern&quot;&gt;&lt;a href=&quot;#cache-aside-pattern&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache Aside Pattern&lt;/h4&gt;
&lt;p&gt;最经典的缓存 + 数据库读写的模式，就是 Cache Aside Pattern。&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;p&gt;另外更新缓存的代价有时候是很高的。是不是说，每次修改数据库的时候，都一定要将其对应的缓存更新一份？也许有的场景是这样，但是对于比较复杂的缓存数据计算的场景，就不是这样了。如果你频繁修改一个缓存涉及的多个表，缓存也频繁更新。但是问题在于，这个缓存到底会不会被频繁访问到？&lt;/p&gt;
&lt;p&gt;举个栗子，一个缓存涉及的表的字段，在 1 分钟内就修改了 20 次，或者是 100 次，那么缓存更新 20 次、100 次；但是这个缓存在 1 分钟内只被读取了 1 次，有大量的冷数据。实际上，如果你只是删除缓存的话，那么在 1 分钟内，这个缓存不过就重新计算一次而已，开销大幅度降低。用到缓存才去算缓存。&lt;/p&gt;
&lt;p&gt;其实删除缓存，而不是更新缓存，就是一个 lazy 计算的思想，不要每次都重新做复杂的计算，不管它会不会用到，而是让它到需要被使用的时候再重新计算。像 mybatis，hibernate，都有懒加载思想。查询一个部门，部门带了一个员工的 list，没有必要说每次查询部门，都把里面的 1000 个员工的数据也同时查出来啊。80% 的情况，查这个部门，就只是要访问这个部门的信息就可以了。先查部门，同时要访问里面的员工，那么这个时候只有在你要访问里面的员工的时候，才会去数据库里面查询 1000 个员工。&lt;/p&gt;
&lt;h4 id=&quot;最初级的缓存不一致问题及解决方案&quot;&gt;&lt;a href=&quot;#%E6%9C%80%E5%88%9D%E7%BA%A7%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%8D%E4%B8%80%E8%87%B4%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;最初级的缓存不一致问题及解决方案&lt;/h4&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;延时双删。依旧是先更新数据库，再删除缓存，唯一不同的是，我们把这个删除的动作，在不久之后再执行一次，比如 5s 之后。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32198044032830554000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public void set(key, value) {
  putToDb(key, value);
  deleteFromRedis(key);

  // ... a few seconds later
  deleteFromRedis(key);
}`, `32198044032830554000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;putToDb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;deleteFromRedis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ... a few seconds later&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;deleteFromRedis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;删除的动作，可以有多种选择，比如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 DelayQueue，会随着 JVM 进程的死亡，丢失更新的风险；&lt;/li&gt;
&lt;li&gt;放在 MQ，但编码复杂度增加。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，我们需要综合各种因素去做设计，选择一个最合理的解决方案。&lt;/p&gt;
&lt;h4 id=&quot;比较复杂的数据不一致问题分析&quot;&gt;&lt;a href=&quot;#%E6%AF%94%E8%BE%83%E5%A4%8D%E6%9D%82%E7%9A%84%E6%95%B0%E6%8D%AE%E4%B8%8D%E4%B8%80%E8%87%B4%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;比较复杂的数据不一致问题分析&lt;/h4&gt;
&lt;p&gt;数据发生了变更，先删除了缓存，然后要去修改数据库，此时还没修改。一个请求过来，去读缓存，发现缓存空了，去查询数据库，查到了修改前的旧数据，放到了缓存中。随后数据变更的程序完成了数据库的修改。完了，数据库和缓存中的数据不一样了…&lt;/p&gt;
&lt;p&gt;为什么上亿流量高并发场景下，缓存会出现这个问题？&lt;/p&gt;
&lt;p&gt;只有在对一个数据并发的进行读写的时候，才可能会出现这种问题。其实如果说你的并发量很低的话，特别是读并发很低，每天访问量就 1 万次，那么很少的情况下，不会出现刚才描述的那种不一致的场景。但是问题是，如果每天的是上亿的流量，每秒并发读是几万，每秒只要有数据更新的请求，就可能会出现上述的数据库 + 缓存不一致的情况。&lt;/p&gt;
&lt;p&gt;解决方案如下：&lt;/p&gt;
&lt;p&gt;更新数据的时候，根据数据的唯一标识，将操作路由之后，发送到一个 jvm 内部队列中。读取数据的时候，如果发现数据不在缓存中，那么将重新执行“读取数据 + 更新缓存”的操作，根据唯一标识路由之后，也发送到同一个 jvm 内部队列中。&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;p&gt;读请求长时阻塞&lt;/p&gt;
&lt;p&gt;由于读请求进行了非常轻度的异步化，所以一定要注意读超时的问题，每个读请求必须在超时时间范围内返回。&lt;/p&gt;
&lt;p&gt;该解决方案，最大的风险点在于说，可能数据更新很频繁，导致队列中积压了大量更新操作在里面，然后读请求会发生大量的超时，最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试，看看更新数据的频率是怎样的。&lt;/p&gt;
&lt;p&gt;另外一点，因为一个队列中，可能会积压针对多个数据项的更新操作，因此需要根据自己的业务情况进行测试，可能需要部署多个服务，每个服务分摊一些数据的更新操作。如果一个内存队列里会挤压 100 个商品的库存修改操作，每个库存修改操作要耗费 10ms 去完成，那么最后一个商品的读请求，可能等待 &lt;code class=&quot;language-text&quot;&gt;10 * 100 = 1000ms = 1s&lt;/code&gt; 后，才能得到数据，这个时候就导致读请求的长时阻塞。&lt;/p&gt;
&lt;p&gt;一定要根据实际业务系统的运行情况，去进行一些压力测试，和模拟线上环境，去看看最繁忙的时候，内存队列可能会挤压多少更新操作，可能会导致最后一个更新操作对应的读请求，会 hang 多少时间，如果读请求在 200ms 返回，如果你计算过后，哪怕是最繁忙的时候，积压 10 个更新操作，最多等待 200ms，那还可以的。&lt;/p&gt;
&lt;p&gt;如果一个内存队列中可能积压的更新操作特别多，那么你就要加机器，让每个机器上部署的服务实例处理更少的数据，那么每个内存队列中积压的更新操作就会越少。&lt;/p&gt;
&lt;p&gt;其实根据之前的项目经验，一般来说，数据的写频率是很低的，因此实际上正常来说，在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的项目，一般来说写请求是非常少的，每秒的 QPS 能到几百就不错了。&lt;/p&gt;
&lt;p&gt;我们来实际粗略测算一下。&lt;/p&gt;
&lt;p&gt;如果一秒有 500 的写操作，如果分成 5 个时间片，每 200ms 就 100 个写操作，放到 20 个内存队列中，每个内存队列，可能就积压 5 个写操作。每个写操作性能测试后，一般是在 20ms 左右就完成，那么针对每个内存队列的数据的读请求，也就最多 hang 一会儿，200ms 以内肯定能返回了。&lt;/p&gt;
&lt;p&gt;经过刚才简单的测算，我们知道，单机支撑的写 QPS 在几百是没问题的，如果写 QPS 扩大了 10 倍，那么就扩容机器，扩容 10 倍的机器，每个机器 20 个队列。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读请求并发量过高&lt;/p&gt;
&lt;p&gt;这里还必须做好压力测试，确保恰巧碰上上述情况的时候，还有一个风险，就是突然间大量读请求会在几十毫秒的延时 hang 在服务上，看服务能不能扛的住，需要多少机器才能扛住最大的极限情况的峰值。&lt;/p&gt;
&lt;p&gt;但是因为并不是所有的数据都在同一时间更新，缓存也不会同一时间失效，所以每次可能也就是少数数据的缓存失效了，然后那些数据对应的读请求过来，并发量应该也不会特别大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多服务实例部署的请求路由&lt;/p&gt;
&lt;p&gt;可能这个服务部署了多个实例，那么必须保证说，执行数据更新操作，以及执行缓存更新操作的请求，都通过 Nginx 服务器路由到相同的服务实例上。&lt;/p&gt;
&lt;p&gt;比如说，对同一个商品的读写请求，全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由，也可以用 Nginx 的 hash 路由功能等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;热点商品的路由问题，导致请求的倾斜&lt;/p&gt;
&lt;p&gt;万一某个商品的读写请求特别高，全部打到相同机器的相同队列里面去了，可能会造成某台机器的压力过大。就是说，因为只有在商品数据更新的时候才会清空缓存，然后才会导致读写并发，所以其实要根据业务系统去看，如果更新频率不是太高的话，这个问题的影响并不是特别大，但是的确可能某些机器的负载会高一些。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;如何解决-redis-的并发竞争问题？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3-redis-%E7%9A%84%E5%B9%B6%E5%8F%91%E7%AB%9E%E4%BA%89%E9%97%AE%E9%A2%98%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何解决 Redis 的并发竞争问题？&lt;/h3&gt;
&lt;p&gt;多客户端同时并发写一个 key，可能本来应该先到的数据后到了，导致数据版本错了；或者是多客户端同时获取一个 key，修改值之后再写回去，只要顺序错了，数据就错了。&lt;/p&gt;
&lt;p&gt;而且 Redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;某个时刻，多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁，确保同一时间，只能有一个系统实例在操作某个 key，别人都不允许读和写。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你要写入缓存的数据，都是从 mysql 里查出来的，都得写入 mysql 中，写入 mysql 中的时候必须保存一个时间戳，从 mysql 查出来的时候，时间戳也查出来。&lt;/p&gt;
&lt;p&gt;每次要写之前，先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话，那么可以写，否则，就不能用旧的数据覆盖新的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;生产环境中的-redis-是怎么部署的？&quot;&gt;&lt;a href=&quot;#%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E7%9A%84-redis-%E6%98%AF%E6%80%8E%E4%B9%88%E9%83%A8%E7%BD%B2%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;生产环境中的 Redis 是怎么部署的？&lt;/h3&gt;
&lt;p&gt;Redis cluster，10 台机器，5 台机器部署了 Redis 主实例，另外 5 台机器部署了 Redis 的从实例，每个主实例挂了一个从实例，5 个节点对外提供读写服务，每个节点的读写高峰 QPS 可以达到每秒 5 万，5 台机器最多是 25 万读写请求每秒。&lt;/p&gt;
&lt;p&gt;机器是什么配置？&lt;/p&gt;
&lt;p&gt;32G 内存 + 8 核 CPU + 1T 磁盘，但是分配给 Redis 进程的是 10g 内存，一般线上生产环境，Redis 的内存尽量不要超过 10g，超过 10g 可能会有问题。&lt;/p&gt;
&lt;p&gt;5 台机器对外提供读写，一共有 50g 内存。&lt;/p&gt;
&lt;p&gt;因为每个主实例都挂了一个从实例，所以是高可用的，任何一个主实例宕机，都会自动故障迁移，Redis 从实例会自动变成主实例继续提供读写服务。&lt;/p&gt;
&lt;p&gt;你往内存里写的是什么数据？每条数据的大小是多少？&lt;/p&gt;
&lt;p&gt;商品数据，每条数据是 10kb。100 条数据是 1mb，10 万条数据是 1g。常驻内存的是 200 万条商品数据，占用内存是 20g，仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。&lt;/p&gt;
&lt;p&gt;其实大型的公司，会有基础架构的 team 负责缓存集群的运维。&lt;/p&gt;
&lt;h2 id=&quot;分库分表&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分库分表&lt;/h2&gt;
&lt;h3 id=&quot;为什么要分库分表？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为什么要分库分表？&lt;/h3&gt;
&lt;p&gt;说白了，分库分表是两回事儿，大家可别搞混了，可能是光分库不分表，也可能是光分表不分库，都有可能。&lt;/p&gt;
&lt;p&gt;我先给大家抛出来一个场景。&lt;/p&gt;
&lt;p&gt;假如我们现在是一个小创业公司（或者是一个 BAT 公司刚兴起的一个新部门），现在注册用户就 20 万，每天活跃用户就 1 万，每天单表数据量就 1000，然后高峰期每秒钟并发请求最多就 10 个。我的天，就这种系统，随便找一个有几年工作经验的，然后带几个刚培训出来的，随便干干都可以。&lt;/p&gt;
&lt;p&gt;结果没想到我们运气居然这么好，碰上个 CEO 带着我们走上了康庄大道，业务发展迅猛，过了几个月，注册用户数达到了 2000 万！每天活跃用户数 100 万！每天单表数据量 10 万条！高峰期每秒最大请求达到 1000！同时公司还顺带着融资了两轮，进账了几个亿人民币啊！公司估值达到了惊人的几亿美金！这是小独角兽的节奏！&lt;/p&gt;
&lt;p&gt;好吧，没事，现在大家感觉压力已经有点大了，为啥呢？因为每天多 10 万条数据，一个月就多 300 万条数据，现在咱们单表已经几百万数据了，马上就破千万了。但是勉强还能撑着。高峰期请求现在是 1000，咱们线上部署了几台机器，负载均衡搞了一下，数据库撑 1000QPS 也还凑合。但是大家现在开始感觉有点担心了，接下来咋整呢…&lt;/p&gt;
&lt;p&gt;再接下来几个月，我的天，CEO 太牛逼了，公司用户数已经达到 1 亿，公司继续融资几十亿人民币啊！公司估值达到了惊人的几十亿美金，成为了国内今年最牛逼的明星创业公司！天，我们太幸运了。&lt;/p&gt;
&lt;p&gt;但是我们同时也是不幸的，因为此时每天活跃用户数上千万，每天单表新增数据多达 50 万，目前一个表总数据量都已经达到了两三千万了！扛不住啊！数据库磁盘容量不断消耗掉！高峰期并发达到惊人的 5000 ~ 8000！别开玩笑了，哥。我跟你保证，你的系统支撑不到现在，已经挂掉了！&lt;/p&gt;
&lt;p&gt;好吧，所以你看到这里差不多就理解分库分表是怎么回事儿了，实际上这是跟着你的公司业务发展走的，你公司业务发展越好，用户就越多，数据量越大，请求量越大，那你单个数据库一定扛不住。&lt;/p&gt;
&lt;h4 id=&quot;分表&quot;&gt;&lt;a href=&quot;#%E5%88%86%E8%A1%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分表&lt;/h4&gt;
&lt;p&gt;比如你单表都几千万数据了，你确定你能扛住么？绝对不行，单表数据量太大，会极大影响你的 sql 执行的性能，到了后面你的 sql 可能就跑的很慢了。一般来说，就以我的经验来看，单表到几百万的时候，性能就会相对差一些了，你就得分表了。&lt;/p&gt;
&lt;p&gt;分表是啥意思？就是把一个表的数据放到多个表中，然后查询的时候你就查一个表。比如按照用户 id 来分表，将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内，比如每个表就固定在 200 万以内。&lt;/p&gt;
&lt;h4 id=&quot;分库&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%BA%93&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分库&lt;/h4&gt;
&lt;p&gt;分库是啥意思？就是你一个库一般我们经验而言，最多支撑到并发 2000，一定要扩容了，而且一个健康的单库并发值你最好保持在每秒 1000 左右，不要太大。那么你可以将一个库的数据拆分到多个库中，访问的时候就访问一个库好了。&lt;/p&gt;
&lt;p&gt;这就是所谓的分库分表，为啥要分库分表？你明白了吧。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;分库分表前&lt;/th&gt;
&lt;th&gt;分库分表后&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;并发支撑情况&lt;/td&gt;
&lt;td&gt;MySQL 单机部署，扛不住高并发&lt;/td&gt;
&lt;td&gt;MySQL 从单机到多机，能承受的并发增加了多倍&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;磁盘使用情况&lt;/td&gt;
&lt;td&gt;MySQL 单机磁盘容量几乎撑满&lt;/td&gt;
&lt;td&gt;拆分为多个库，数据库服务器磁盘使用率大大降低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL 执行性能&lt;/td&gt;
&lt;td&gt;单表数据量太大，SQL 越跑越慢&lt;/td&gt;
&lt;td&gt;单表数据量减少，SQL 执行效率明显提升&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;用过哪些分库分表中间件？不同的分库分表中间件都有什么优点和缺点？&quot;&gt;&lt;a href=&quot;#%E7%94%A8%E8%BF%87%E5%93%AA%E4%BA%9B%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%AD%E9%97%B4%E4%BB%B6%EF%BC%9F%E4%B8%8D%E5%90%8C%E7%9A%84%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%AD%E9%97%B4%E4%BB%B6%E9%83%BD%E6%9C%89%E4%BB%80%E4%B9%88%E4%BC%98%E7%82%B9%E5%92%8C%E7%BC%BA%E7%82%B9%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;用过哪些分库分表中间件？不同的分库分表中间件都有什么优点和缺点？&lt;/h4&gt;
&lt;p&gt;这个其实就是看看你了解哪些分库分表的中间件，各个中间件的优缺点是啥？然后你用过哪些分库分表的中间件。&lt;/p&gt;
&lt;p&gt;比较常见的包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cobar&lt;/li&gt;
&lt;li&gt;TDDL&lt;/li&gt;
&lt;li&gt;Atlas&lt;/li&gt;
&lt;li&gt;Sharding-jdbc&lt;/li&gt;
&lt;li&gt;Mycat&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;cobar&quot;&gt;&lt;a href=&quot;#cobar&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cobar&lt;/h5&gt;
&lt;p&gt;阿里 b2b 团队开发和开源的，属于 proxy 层方案，就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群，Cobar 根据 SQL 和分库规则对 SQL 做分解，然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用，但是最近几年都没更新了，基本没啥人用，差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。&lt;/p&gt;
&lt;h5 id=&quot;tddl&quot;&gt;&lt;a href=&quot;#tddl&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TDDL&lt;/h5&gt;
&lt;p&gt;淘宝团队开发的，属于 client 层方案。支持基本的 crud 语法和读写分离，但不支持 join、多表查询等语法。目前使用的也不多，因为还依赖淘宝的 diamond 配置管理系统。&lt;/p&gt;
&lt;h5 id=&quot;atlas&quot;&gt;&lt;a href=&quot;#atlas&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Atlas&lt;/h5&gt;
&lt;p&gt;360 开源的，属于 proxy 层方案，以前是有一些公司在用的，但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以，现在用的公司基本也很少了。&lt;/p&gt;
&lt;h5 id=&quot;sharding-jdbc&quot;&gt;&lt;a href=&quot;#sharding-jdbc&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sharding-jdbc&lt;/h5&gt;
&lt;p&gt;当当开源的，属于 client 层方案，是 ShardingSphere 的 client 层方案，ShardingSphere 还提供 proxy 层的方案 Sharding-Proxy。确实之前用的还比较多一些，因为 SQL 语法支持也比较多，没有太多限制，而且截至 2019.4，已经推出到了 4.0.0-RC1 版本，支持分库分表、读写分离、分布式 id 生成、柔性事务（最大努力送达型事务、TCC 事务）。而且确实之前使用的公司会比较多一些（这个在官网有登记使用的公司，可以看到从 2017 年一直到现在，是有不少公司在用的），目前社区也还一直在开发和维护，还算是比较活跃，个人认为算是一个现在也可以选择的方案。&lt;/p&gt;
&lt;h5 id=&quot;mycat&quot;&gt;&lt;a href=&quot;#mycat&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mycat&lt;/h5&gt;
&lt;p&gt;基于 Cobar 改造的，属于 proxy 层方案，支持的功能非常完善，而且目前应该是非常火的而且不断流行的数据库中间件，社区很活跃，也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说，年轻一些，经历的锤炼少一些。&lt;/p&gt;
&lt;h5 id=&quot;总结-1&quot;&gt;&lt;a href=&quot;#%E6%80%BB%E7%BB%93-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;总结&lt;/h5&gt;
&lt;p&gt;综上，现在其实建议考量的，就是 Sharding-jdbc 和 Mycat，这两个都可以去考虑使用。&lt;/p&gt;
&lt;p&gt;Sharding-jdbc 这种 client 层方案的优点在于不用部署，运维成本低，不需要代理层的二次转发请求，性能很高，但是如果遇到升级啥的需要各个系统都重新升级版本再发布，各个系统都需要耦合 Sharding-jdbc 的依赖；&lt;/p&gt;
&lt;p&gt;Mycat 这种 proxy 层方案的缺点在于需要部署，自己运维一套中间件，运维成本高，但是好处在于对于各个项目是透明的，如果遇到升级之类的都是自己中间件那里搞就行了。&lt;/p&gt;
&lt;p&gt;通常来说，这两个方案其实都可以选用，但是我个人建议中小型公司选用 Sharding-jdbc，client 层方案轻便，而且维护成本低，不需要额外增派人手，而且中小型公司系统复杂度会低一些，项目也没那么多；但是中大型公司最好还是选用 Mycat 这类 proxy 层方案，因为可能大公司系统和项目非常多，团队很大，人员充足，那么最好是专门弄个人来研究和维护 Mycat，然后大量项目直接透明使用即可。&lt;/p&gt;
&lt;h4 id=&quot;你们具体是如何对数据库如何进行垂直拆分或水平拆分的？&quot;&gt;&lt;a href=&quot;#%E4%BD%A0%E4%BB%AC%E5%85%B7%E4%BD%93%E6%98%AF%E5%A6%82%E4%BD%95%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E5%9E%82%E7%9B%B4%E6%8B%86%E5%88%86%E6%88%96%E6%B0%B4%E5%B9%B3%E6%8B%86%E5%88%86%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;你们具体是如何对数据库如何进行垂直拆分或水平拆分的？&lt;/h4&gt;
&lt;p&gt;水平拆分的意思，就是把一个表的数据给弄到多个库的多个表里去，但是每个库的表结构都一样，只不过每个库表放的数据是不同的，所有库表的数据加起来就是全部数据。水平拆分的意义，就是将数据均匀放更多的库里，然后用多个库来扛更高的并发，还有就是用多个库的存储容量来进行扩容。&lt;/p&gt;
&lt;p&gt;垂直拆分的意思，就是把一个有很多字段的表给拆分成多个表，或者是多个库上去。每个库表的结构都不一样，每个库表都包含部分字段。一般来说，会将较少的访问频率很高的字段放到一个表里去，然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的，你访问频率高的行字段越少，就可以在缓存里缓存更多的行，性能就越好。这个一般在表层面做的较多一些。&lt;/p&gt;
&lt;p&gt;这个其实挺常见的，不一定我说，大家很多同学可能自己都做过，把一个大表拆开，订单表、订单支付表、订单商品表。&lt;/p&gt;
&lt;p&gt;还有表层面的拆分，就是分表，将一个表变成 N 个表，就是让每个表的数据量控制在一定范围内，保证 SQL 的性能。否则单表数据量越大，SQL 性能就越差。一般是 200 万行左右，不要太多，但是也得看具体你怎么操作，也可能是 500 万，或者是 100 万。你的 SQL 越复杂，就最好让单表行数越少。&lt;/p&gt;
&lt;p&gt;好了，无论分库还是分表，上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后，中间件可以根据你指定的某个字段值，比如说 userid，自动路由到对应的库上去，然后再自动路由到对应的表里去。&lt;/p&gt;
&lt;p&gt;你就得考虑一下，你的项目里该如何分库分表？一般来说，垂直拆分，你可以在表层面来做，对一些字段特别多的表做一下拆分；水平拆分，你可以说是并发承载不了，或者是数据量太大，容量承载不了，你给拆了，按什么字段来拆，你自己想好；分表，你考虑一下，你如果哪怕是拆到每个库里去，并发和容量都 ok 了，但是每个库的表还是太大了，那么你就分表，将这个表分开，保证每个表的数据量并不是很大。&lt;/p&gt;
&lt;p&gt;而且这儿还有两种分库分表的方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按照 range 来分，就是每个库一段连续的数据，这个一般是按比如时间范围来的，但是这种一般较少用，因为很容易产生热点问题，大量的流量都打在最新的数据上了。&lt;/li&gt;
&lt;li&gt;按照某个字段 hash 一下均匀分散，这个较为常用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;range 来分，好处在于说，扩容的时候很简单，因为你只要预备好，给每个月都准备一个库就可以了，到了一个新的月份的时候，自然而然，就会写新的库了；缺点，但是大部分的请求，都是访问最新的数据。实际生产用 range，要看场景。&lt;/p&gt;
&lt;p&gt;hash 分发，好处在于说，可以平均分配每个库的数据量和请求压力；坏处在于说扩容起来比较麻烦，会有一个数据迁移的过程，之前的数据需要重新计算 hash 值重新分配到不同的库或表。&lt;/p&gt;
&lt;h3 id=&quot;分库分表如何平滑过渡？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E5%A6%82%E4%BD%95%E5%B9%B3%E6%BB%91%E8%BF%87%E6%B8%A1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分库分表如何平滑过渡？&lt;/h3&gt;
&lt;h4 id=&quot;停机迁移方案&quot;&gt;&lt;a href=&quot;#%E5%81%9C%E6%9C%BA%E8%BF%81%E7%A7%BB%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;停机迁移方案&lt;/h4&gt;
&lt;p&gt;我先给你说一个最 low 的方案，就是很简单，大家伙儿凌晨 12 点开始运维，网站或者 app 挂个公告，说 0 点到早上 6 点进行运维，无法访问。&lt;/p&gt;
&lt;p&gt;接着到 0 点停机，系统停掉，没有流量写入了，此时老的单库单表数据库静止了。然后你之前得写好一个导数的一次性工具，此时直接跑起来，然后将单库单表的数据哗哗哗读出来，写到分库分表里面去。&lt;/p&gt;
&lt;p&gt;导数完了之后，就 ok 了，修改系统的数据库连接配置啥的，包括可能代码和 SQL 也许有修改，那你就用最新的代码，然后直接启动连到新的分库分表上去。&lt;/p&gt;
&lt;p&gt;验证一下，ok 了，完美，大家伸个懒腰，看看看凌晨 4 点钟的北京夜景，打个滴滴回家吧。&lt;/p&gt;
&lt;p&gt;但是这个方案比较 low，谁都能干，我们来看看高大上一点的方案。&lt;/p&gt;
&lt;h4 id=&quot;双写迁移方案&quot;&gt;&lt;a href=&quot;#%E5%8F%8C%E5%86%99%E8%BF%81%E7%A7%BB%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;双写迁移方案&lt;/h4&gt;
&lt;p&gt;这个是我们常用的一种迁移方案，比较靠谱一些，不用停机，不用看北京凌晨 4 点的风景。&lt;/p&gt;
&lt;p&gt;简单来说，就是在线上系统里面，之前所有写库的地方，增删改操作，除了对老库增删改，都加上对新库的增删改，这就是所谓的双写，同时写俩库，老库和新库。&lt;/p&gt;
&lt;p&gt;然后系统部署之后，新库数据差太远，用之前说的导数工具，跑起来读老库数据写新库，写的时候要根据 &lt;code class=&quot;language-text&quot;&gt;gmt_modified&lt;/code&gt; 这类字段判断这条数据最后修改的时间，除非是读出来的数据在新库里没有，或者是比新库的数据新才会写。简单来说，就是不允许用老数据覆盖新数据。&lt;/p&gt;
&lt;p&gt;导完一轮之后，有可能数据还是存在不一致，那么就程序自动做一轮校验，比对新老库每个表的每条数据，接着如果有不一样的，就针对那些不一样的，从老库读数据再次写。反复循环，直到两个库每个表的数据都完全一致为止。&lt;/p&gt;
&lt;p&gt;接着当数据完全一致了，就 ok 了，基于仅仅使用分库分表的最新代码，重新部署一次，不就仅仅基于分库分表在操作了么，还没有几个小时的停机时间，很稳。所以现在基本玩儿数据迁移之类的，都是这么干的。&lt;/p&gt;
&lt;h3 id=&quot;如何设计可以动态扩容缩容的分库分表方案？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E5%8F%AF%E4%BB%A5%E5%8A%A8%E6%80%81%E6%89%A9%E5%AE%B9%E7%BC%A9%E5%AE%B9%E7%9A%84%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E6%96%B9%E6%A1%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何设计可以动态扩容缩容的分库分表方案？&lt;/h3&gt;
&lt;p&gt;对于分库分表来说，主要是面对以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选择一个数据库中间件，调研、学习、测试；&lt;/li&gt;
&lt;li&gt;设计你的分库分表的一个方案，你要分成多少个库，每个库分成多少个表，比如 3 个库，每个库 4 个表；&lt;/li&gt;
&lt;li&gt;基于选择好的数据库中间件，以及在测试环境建立好的分库分表的环境，然后测试一下能否正常进行分库分表的读写；&lt;/li&gt;
&lt;li&gt;完成单库单表到分库分表的迁移，双写方案；&lt;/li&gt;
&lt;li&gt;线上系统开始基于分库分表对外提供服务；&lt;/li&gt;
&lt;li&gt;扩容了，扩容成 6 个库，每个库需要 12 个表，你怎么来增加更多库和表呢？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个是你必须面对的一个事儿，就是你已经弄好分库分表方案了，然后一堆库和表都建好了，基于分库分表中间件的代码开发啥的都好了，测试都 ok 了，数据能均匀分布到各个库和各个表里去，而且接着你还通过双写的方案咔嚓一下上了系统，已经直接基于分库分表方案在搞了。&lt;/p&gt;
&lt;p&gt;那么现在问题来了，你现在这些库和表又支撑不住了，要继续扩容咋办？这个可能就是说你的每个库的容量又快满了，或者是你的表数据量又太大了，也可能是你每个库的写并发太高了，你得继续扩容。&lt;/p&gt;
&lt;h4 id=&quot;停机扩容（不推荐）&quot;&gt;&lt;a href=&quot;#%E5%81%9C%E6%9C%BA%E6%89%A9%E5%AE%B9%EF%BC%88%E4%B8%8D%E6%8E%A8%E8%8D%90%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;停机扩容（不推荐）&lt;/h4&gt;
&lt;p&gt;这个方案就跟停机迁移一样，步骤几乎一致，唯一的一点就是那个导数的工具，是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿，有点不太靠谱，因为既然分库分表就说明数据量实在是太大了，可能多达几亿条，甚至几十亿，你这么玩儿，可能会出问题。&lt;/p&gt;
&lt;p&gt;从单库单表迁移到分库分表的时候，数据量并不是很大，单表最大也就两三千万。那么你写个工具，多弄几台机器并行跑，1 小时数据就导完了。这没有问题。&lt;/p&gt;
&lt;p&gt;如果 3 个库 + 12 个表，跑了一段时间了，数据量都 1 ~ 2 亿了。光是导 2 亿数据，都要导个几个小时，6 点，刚刚导完数据，还要搞后续的修改配置，重启系统，测试验证，10 点才可以搞完。所以不能这么搞。&lt;/p&gt;
&lt;h4 id=&quot;优化后的方案&quot;&gt;&lt;a href=&quot;#%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;优化后的方案&lt;/h4&gt;
&lt;p&gt;一开始上来就是 32 个库，每个库 32 个表，那么总共是 1024 张表。&lt;/p&gt;
&lt;p&gt;我可以告诉各位同学，这个分法，第一，基本上国内的互联网肯定都是够用了，第二，无论是并发支撑还是数据量支撑都没问题。&lt;/p&gt;
&lt;p&gt;每个库正常承载的写入并发量是 1000，那么 32 个库就可以承载 &lt;code class=&quot;language-text&quot;&gt;32 * 1000 = 32000&lt;/code&gt; 的写并发，如果每个库承载 1500 的写并发，&lt;code class=&quot;language-text&quot;&gt;32 * 1500 = 48000&lt;/code&gt; 的写并发，接近 5 万每秒的写入并发，前面再加一个 MQ，削峰，每秒写入 MQ 8 万条数据，每秒消费 5 万条数据。&lt;/p&gt;
&lt;p&gt;有些除非是国内排名非常靠前的这些公司，他们的最核心的系统的数据库，可能会出现几百台数据库的这么一个规模，128 个库，256 个库，512 个库。&lt;/p&gt;
&lt;p&gt;1024 张表，假设每个表放 500 万数据，在 MySQL 里可以放 50 亿条数据。&lt;/p&gt;
&lt;p&gt;每秒 5 万的写并发，总共 50 亿条数据，对于国内大部分的互联网公司来说，其实一般来说都够了。&lt;/p&gt;
&lt;p&gt;谈分库分表的扩容，第一次分库分表，就一次性给他分个够，32 个库，1024 张表，可能对大部分的中小型互联网公司来说，已经可以支撑好几年了。&lt;/p&gt;
&lt;p&gt;一个实践是利用 &lt;code class=&quot;language-text&quot;&gt;32 * 32&lt;/code&gt; 来分库分表，即分为 32 个库，每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库，再根据 32 取模路由到库里的表。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;orderId&lt;/th&gt;
&lt;th&gt;id % 32 (库)&lt;/th&gt;
&lt;th&gt;id / 32 % 32 (表)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;259&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1189&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;352&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4593&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;刚开始的时候，这个库可能就是逻辑库，建在一个数据库上的，就是一个 MySQL 服务器可能建了 n 个库，比如 32 个库。后面如果要拆分，就是不断在库和 MySQL 服务器之间做迁移就可以了。然后系统配合改一下配置即可。&lt;/p&gt;
&lt;p&gt;比如说最多可以扩展到 32 个数据库服务器，每个数据库服务器是一个库。如果还是不够？最多可以扩展到 1024 个数据库服务器，每个数据库服务器上面一个库一个表。因为最多是 1024 个表。&lt;/p&gt;
&lt;p&gt;这么搞，是不用自己写代码做数据迁移的，都交给 DBA 来搞好了，但是 DBA 确实是需要做一些库表迁移的工作，但是总比你自己写代码，然后抽数据导数据来的效率高得多吧。&lt;/p&gt;
&lt;p&gt;哪怕是要减少库的数量，也很简单，其实说白了就是按倍数缩容就可以了，然后修改一下路由规则。&lt;/p&gt;
&lt;p&gt;这里对步骤做一个总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设定好几台数据库服务器，每台服务器上几个库，每个库多少个表，推荐是 &lt;code class=&quot;language-text&quot;&gt;32 库 * 32 表&lt;/code&gt;，对于大部分公司来说，可能几年都够了。&lt;/li&gt;
&lt;li&gt;路由的规则，orderId % 32 = 库，orderId / 32 % 32 = 表&lt;/li&gt;
&lt;li&gt;扩容的时候，申请增加更多的数据库服务器，装好 MySQL，呈倍数扩容，4 台服务器，扩到 8 台服务器，再到 16 台服务器。&lt;/li&gt;
&lt;li&gt;由 DBA 负责将原先数据库服务器的库，迁移到新的数据库服务器上去，库迁移是有一些便捷的工具的。&lt;/li&gt;
&lt;li&gt;我们这边就是修改一下配置，调整迁移的库所在数据库服务器的地址。&lt;/li&gt;
&lt;li&gt;重新发布系统，上线，原先的路由规则变都不用变，直接可以基于 n 倍的数据库服务器的资源，继续进行线上系统的提供服务。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;分库分表之后，id-主键如何处理？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B9%8B%E5%90%8E%EF%BC%8Cid-%E4%B8%BB%E9%94%AE%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分库分表之后，id 主键如何处理？&lt;/h3&gt;
&lt;h4 id=&quot;基于数据库的实现方案&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于数据库的实现方案&lt;/h4&gt;
&lt;h5 id=&quot;数据库自增-id&quot;&gt;&lt;a href=&quot;#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%87%AA%E5%A2%9E-id&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;数据库自增 id&lt;/h5&gt;
&lt;p&gt;这个就是说你的系统里每次得到一个 id，都是往一个库的一个表里插入一条没什么业务含义的数据，然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。&lt;/p&gt;
&lt;p&gt;这个方案的好处就是方便简单，谁都会用；缺点就是单库生成自增 id，要是高并发的话，就会有瓶颈的；如果你硬是要改进一下，那么就专门开一个服务出来，这个服务每次就拿到当前 id 最大值，然后自己递增几个 id，一次性返回一批 id，然后再把当前最大 id 值修改成递增几个 id 之后的一个值；但是无论如何都是基于单个数据库。&lt;/p&gt;
&lt;p&gt;适合的场景：你分库分表就俩原因，要不就是单库并发太高，要不就是单库数据量太大；除非是你并发不高，但是数据量太大导致的分库分表扩容，你可以用这个方案，因为可能每秒最高并发最多就几百，那么就走单独的一个库和表生成自增主键即可。&lt;/p&gt;
&lt;h5 id=&quot;设置数据库-sequence-或者表自增字段步长&quot;&gt;&lt;a href=&quot;#%E8%AE%BE%E7%BD%AE%E6%95%B0%E6%8D%AE%E5%BA%93-sequence-%E6%88%96%E8%80%85%E8%A1%A8%E8%87%AA%E5%A2%9E%E5%AD%97%E6%AE%B5%E6%AD%A5%E9%95%BF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;设置数据库 sequence 或者表自增字段步长&lt;/h5&gt;
&lt;p&gt;可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。&lt;/p&gt;
&lt;p&gt;比如说，现在有 8 个服务节点，每个服务节点使用一个 sequence 功能来产生 ID，每个 sequence 的起始 ID 不同，并且依次递增，步长都是 8。&lt;/p&gt;
&lt;p&gt;适合的场景：在用户防止产生的 ID 重复时，这种方案实现起来比较简单，也能达到性能目标。但是服务节点固定，步长也固定，将来如果还要增加服务节点，就不好搞了。&lt;/p&gt;
&lt;h4 id=&quot;uuid&quot;&gt;&lt;a href=&quot;#uuid&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;UUID&lt;/h4&gt;
&lt;p&gt;好处就是本地生成，不要基于数据库来了；不好之处就是，UUID 太长了、占用空间大，作为主键性能太差了；更重要的是，UUID 不具有有序性，会导致 B+ 树索引在写的时候有过多的随机写操作（连续的 ID 可以产生部分顺序写），还有，由于在写的时候不能产生有顺序的 append 操作，而需要进行 insert 操作，将会读取整个 B+ 树节点到内存，在插入这条记录后会将整个节点写回磁盘，这种操作在记录占用空间比较大的情况下，性能下降明显。&lt;/p&gt;
&lt;p&gt;适合的场景：如果你是要随机生成个什么文件名、编号之类的，你可以用 UUID，但是作为主键是不能用 UUID 的。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;16731058187518233000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`UUID.randomUUID().toString().replace(&amp;quot;-&amp;quot;, &amp;quot;&amp;quot;) -&gt; sfsdf23423rr234sfdaf`, `16731058187518233000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;UUID&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; sfsdf23423rr234sfdaf&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;获取系统当前时间&quot;&gt;&lt;a href=&quot;#%E8%8E%B7%E5%8F%96%E7%B3%BB%E7%BB%9F%E5%BD%93%E5%89%8D%E6%97%B6%E9%97%B4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;获取系统当前时间&lt;/h4&gt;
&lt;p&gt;这个就是获取当前时间即可，但是问题是，并发很高的时候，比如一秒并发几千，会有重复的情况，这个是肯定不合适的。基本就不用考虑了。&lt;/p&gt;
&lt;p&gt;适合的场景：一般如果用这个方案，是将当前时间跟很多其他的业务字段拼接起来，作为一个 id，如果业务上你觉得可以接受，那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来，组成一个全局唯一的编号。&lt;/p&gt;
&lt;h4 id=&quot;snowflake-算法&quot;&gt;&lt;a href=&quot;#snowflake-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;snowflake 算法&lt;/h4&gt;
&lt;p&gt;snowflake 算法是 twitter 开源的分布式 id 生成算法，采用 Scala 语言实现，是把一个 64 位的 long 型的 id，1 个 bit 是不用的，用其中的 41 bits 作为毫秒数，用 10 bits 作为工作机器 id，12 bits 作为序列号。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 bit：不用，为啥呢？因为二进制里第一个 bit 为如果是 1，那么都是负数，但是我们生成的 id 都是正数，所以第一个 bit 统一都是 0。&lt;/li&gt;
&lt;li&gt;41 bits：表示的是时间戳，单位是毫秒。41 bits 可以表示的数字多达 2^41 - 1 ，也就是可以标识 2^41 - 1 个毫秒值，换算成年就是表示 69 年的时间。&lt;/li&gt;
&lt;li&gt;10 bits：记录工作机器 id，代表的是这个服务最多可以部署在 2^10 台机器上，也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id，5 个 bits 代表机器 id。意思就是最多代表 2^5 个机房（32 个机房），每个机房里可以代表 2^5 个机器（32 台机器）。&lt;/li&gt;
&lt;li&gt;12 bits：这个是用来记录同一个毫秒内产生的不同 id，12 bits 可以代表的最大正整数是 &lt;code class=&quot;language-text&quot;&gt;2^12 - 1 = 4096&lt;/code&gt;，也就是说可以用这个 12 bits 代表的数字来区分同一个毫秒内的 4096 个不同的 id。&lt;/li&gt;
&lt;/ul&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;4179205879163406000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000`, `4179205879163406000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sh&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sh&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sh line-numbers&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;69559691292168410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class IdWorker {

    private long workerId;
    private long datacenterId;
    private long sequence;

    public IdWorker(long workerId, long datacenterId, long sequence) {
        // sanity check for workerId
        // 这儿不就检查了一下，要求就是你传递进来的机房 id 和机器 id 不能超过 32，不能小于 0
        if (workerId &gt; maxWorkerId || workerId &lt; 0) {
            throw new IllegalArgumentException(
                    String.format(&amp;quot;worker Id can&apos;t be greater than %d or less than 0&amp;quot;, maxWorkerId));
        }
        if (datacenterId &gt; maxDatacenterId || datacenterId &lt; 0) {
            throw new IllegalArgumentException(
                    String.format(&amp;quot;datacenter Id can&apos;t be greater than %d or less than 0&amp;quot;, maxDatacenterId));
        }
        System.out.printf(
                &amp;quot;worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d&amp;quot;,
                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    private long twepoch = 1288834974657L;

    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;

    // 这个是二进制运算，就是 5 bit 最多只能有 31 个数字，也就是说机器 id 最多只能是 32 以内
    private long maxWorkerId = -1L ^ (-1L &lt;&lt; workerIdBits);

    // 这个是一个意思，就是 5 bit 最多只能有 31 个数字，机房 id 最多只能是 32 以内
    private long maxDatacenterId = -1L ^ (-1L &lt;&lt; datacenterIdBits);
    private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L &lt;&lt; sequenceBits);

    private long lastTimestamp = -1L;

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

    public synchronized long nextId() {
        // 这儿就是获取当前时间戳，单位是毫秒
        long timestamp = timeGen();

        if (timestamp &lt; lastTimestamp) {
            System.err.printf(&amp;quot;clock is moving backwards.  Rejecting requests until %d.&amp;quot;, lastTimestamp);
            throw new RuntimeException(String.format(
                    &amp;quot;Clock moved backwards.  Refusing to generate id for %d milliseconds&amp;quot;, lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 这个意思是说一个毫秒内最多只能有 4096 个数字
            // 无论你传递多少进来，这个位运算保证始终就是在 4096 这个范围内，避免你自己传递个 sequence 超过了 4096 这个范围
            sequence = (sequence + 1) &amp; sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        // 这儿记录一下最近一次生成 id 的时间戳，单位是毫秒
        lastTimestamp = timestamp;

        // 这儿就是将时间戳左移，放到 41 bit那儿；
        // 将机房 id左移放到 5 bit那儿；
        // 将机器id左移放到 5 bit 那儿；将序号放最后 12 bit；
        // 最后拼接起来成一个 64 bit 的二进制数字，转换成 10 进制就是个 long 型
        return ((timestamp - twepoch) &lt;&lt; timestampLeftShift) | (datacenterId &lt;&lt; datacenterIdShift)
                | (workerId &lt;&lt; workerIdShift) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp &lt;= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    // ---------------测试---------------
    public static void main(String[] args) {
        IdWorker worker = new IdWorker(1, 1, 1);
        for (int i = 0; i &lt; 30; i++) {
            System.out.println(worker.nextId());
        }
    }

}`, `69559691292168410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdWorker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; workerId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; datacenterId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdWorker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; workerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; datacenterId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// sanity check for workerId&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 这儿不就检查了一下，要求就是你传递进来的机房 id 和机器 id 不能超过 32，不能小于 0&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workerId &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; maxWorkerId &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; workerId &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;worker Id can&apos;t be greater than %d or less than 0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxWorkerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;datacenterId &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; maxDatacenterId &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; datacenterId &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;datacenter Id can&apos;t be greater than %d or less than 0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxDatacenterId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                timestampLeftShift&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; datacenterIdBits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; workerIdBits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sequenceBits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; workerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;workerId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workerId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datacenterId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; datacenterId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; twepoch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1288834974657L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; workerIdBits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; datacenterIdBits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 这个是二进制运算，就是 5 bit 最多只能有 31 个数字，也就是说机器 id 最多只能是 32 以内&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; maxWorkerId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; workerIdBits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 这个是一个意思，就是 5 bit 最多只能有 31 个数字，机房 id 最多只能是 32 以内&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; maxDatacenterId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; datacenterIdBits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; sequenceBits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; workerIdShift &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sequenceBits&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; datacenterIdShift &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sequenceBits &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; workerIdBits&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestampLeftShift &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sequenceBits &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; workerIdBits &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; datacenterIdBits&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; sequenceMask &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; sequenceBits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; lastTimestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getWorkerId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; workerId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDatacenterId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; datacenterId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nextId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 这儿就是获取当前时间戳，单位是毫秒&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeGen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;clock is moving backwards.  Rejecting requests until %d.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token string&quot;&gt;&quot;Clock moved backwards.  Refusing to generate id for %d milliseconds&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lastTimestamp &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastTimestamp &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 这个意思是说一个毫秒内最多只能有 4096 个数字&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 无论你传递多少进来，这个位运算保证始终就是在 4096 这个范围内，避免你自己传递个 sequence 超过了 4096 这个范围&lt;/span&gt;
            sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sequence &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; sequenceMask&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sequence &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tilNextMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 这儿记录一下最近一次生成 id 的时间戳，单位是毫秒&lt;/span&gt;
        lastTimestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 这儿就是将时间戳左移，放到 41 bit那儿；&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 将机房 id左移放到 5 bit那儿；&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 将机器id左移放到 5 bit 那儿；将序号放最后 12 bit；&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 最后拼接起来成一个 64 bit 的二进制数字，转换成 10 进制就是个 long 型&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; twepoch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; timestampLeftShift&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;datacenterId &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; datacenterIdShift&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workerId &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; workerIdShift&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; sequence&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tilNextMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeGen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; lastTimestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeGen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeGen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ---------------测试---------------&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;IdWorker&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdWorker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;怎么说呢，大概这个意思吧，就是说 41 bit 是当前毫秒单位的一个时间戳，就这意思；然后 5 bit 是你传递进来的一个机房 id（但是最大只能是 32 以内），另外 5 bit 是你传递进来的机器 id（但是最大只能是 32 以内），剩下的那个 12 bit 序列号，就是如果跟你上次生成 id 的时间还在一个毫秒内，那么会把顺序给你累加，最多在 4096 个序号以内。&lt;/p&gt;
&lt;p&gt;所以你自己利用这个工具类，自己搞一个服务，然后对每个机房的每个机器都初始化这么一个东西，刚开始这个机房的这个机器的序号就是 0。然后每次接收到一个请求，说这个机房的这个机器要生成一个 id，你就找到对应的 Worker 生成。&lt;/p&gt;
&lt;p&gt;利用这个 snowflake 算法，你可以开发自己公司的服务，甚至对于机房 id 和机器 id，反正给你预留了 5 bit + 5 bit，你换成别的有业务含义的东西也可以的。&lt;/p&gt;
&lt;p&gt;这个 snowflake 算法相对来说还是比较靠谱的，所以你要真是搞分布式 id 生成，如果是高并发啥的，那么用这个应该性能比较好，一般每秒几万并发的场景，也足够你用了。&lt;/p&gt;
&lt;h3 id=&quot;读写分离&quot;&gt;&lt;a href=&quot;#%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;读写分离&lt;/h3&gt;
&lt;h4 id=&quot;如何实现-mysql-的读写分离？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-mysql-%E7%9A%84%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何实现 MySQL 的读写分离？&lt;/h4&gt;
&lt;p&gt;其实很简单，就是基于主从复制架构，简单来说，就搞一个主库，挂多个从库，然后我们就单单只是写主库，然后主库会自动把数据给同步到从库上去。&lt;/p&gt;
&lt;h4 id=&quot;mysql-主从复制原理的是啥？&quot;&gt;&lt;a href=&quot;#mysql-%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E5%8E%9F%E7%90%86%E7%9A%84%E6%98%AF%E5%95%A5%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MySQL 主从复制原理的是啥？&lt;/h4&gt;
&lt;p&gt;主库将变更写入 binlog 日志，然后从库连接到主库之后，从库有一个 IO 线程，将主库的 binlog 日志拷贝到自己本地，写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog，然后执行 binlog 日志中的内容，也就是在自己本地再次执行一遍 SQL，这样就可以保证自己跟主库的数据是一样的。&lt;/p&gt;
&lt;p&gt;这里有一个非常重要的一点，就是从库同步主库数据的过程是串行化的，也就是说主库上并行的操作，在从库上会串行执行。所以这就是一个非常重要的点了，由于从库从主库拷贝日志以及串行执行 SQL 的特点，在高并发场景下，从库的数据一定会比主库慢一些，是有延时的。所以经常出现，刚写入主库的数据可能是读不到的，要过几十毫秒，甚至几百毫秒才能读取到。&lt;/p&gt;
&lt;p&gt;而且这里还有另外一个问题，就是如果主库突然宕机，然后恰好数据还没同步到从库，那么有些数据可能在从库上是没有的，有些数据可能就丢失了。&lt;/p&gt;
&lt;p&gt;所以 MySQL 实际上在这一块有两个机制，一个是半同步复制，用来解决主库数据丢失问题；一个是并行复制，用来解决主从同步延时问题。&lt;/p&gt;
&lt;p&gt;这个所谓半同步复制，也叫 semi-sync 复制，指的就是主库写入 binlog 日志之后，就会将强制此时立即将数据同步到从库，从库将日志写入自己本地的 relay log 之后，接着会返回一个 ack 给主库，主库接收到至少一个从库的 ack 之后才会认为写操作完成了。&lt;/p&gt;
&lt;p&gt;所谓并行复制，指的是从库开启多个线程，并行读取 relay log 中不同库的日志，然后并行重放不同库的日志，这是库级别的并行。&lt;/p&gt;
&lt;h4 id=&quot;mysql-主从同步延时问题（精华）&quot;&gt;&lt;a href=&quot;#mysql-%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5%E5%BB%B6%E6%97%B6%E9%97%AE%E9%A2%98%EF%BC%88%E7%B2%BE%E5%8D%8E%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MySQL 主从同步延时问题（精华）&lt;/h4&gt;
&lt;p&gt;以前线上确实处理过因为主从同步延时问题而导致的线上的 bug，属于小型的生产事故。&lt;/p&gt;
&lt;p&gt;是这个么场景。有个同学是这样写代码逻辑的。先插入一条数据，再把它查出来，然后更新这条数据。在生产环境高峰期，写并发达到了 2000/s，这个时候，主从复制延时大概是在小几十毫秒。线上会发现，每天总有那么一些数据，我们期望更新一些重要的数据状态，但在高峰期时候却没更新。用户跟客服反馈，而客服就会反馈给我们。&lt;/p&gt;
&lt;p&gt;我们通过 MySQL 命令：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28579716509882913000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`show slave status`, `28579716509882913000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;sql&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;sql&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-sql line-numbers&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;show&lt;/span&gt; slave &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;查看 &lt;code class=&quot;language-text&quot;&gt;Seconds_Behind_Master&lt;/code&gt;，可以看到从库复制主库的数据落后了几 ms。&lt;/p&gt;
&lt;p&gt;一般来说，如果主从延迟较为严重，有以下解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分库，将一个主库拆分为多个主库，每个主库的写并发就减少了几倍，此时主从延迟可以忽略不计。&lt;/li&gt;
&lt;li&gt;打开 MySQL 支持的并行复制，多个库并行复制。如果说某个库的写入并发就是特别高，单库写并发达到了 2000/s，并行复制还是没意义。&lt;/li&gt;
&lt;li&gt;重写代码，写代码的同学，要慎重，插入数据时立马查询可能查不到。&lt;/li&gt;
&lt;li&gt;如果确实是存在必须先插入，立马要求就查询到，然后立马就要反过来执行一些操作，对这个查询设置直连主库。不推荐这种方法，你要是这么搞，读写分离的意义就丧失了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;高并发系统&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E5%B9%B6%E5%8F%91%E7%B3%BB%E7%BB%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高并发系统&lt;/h2&gt;
&lt;h3 id=&quot;如何设计一个高并发系统？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E9%AB%98%E5%B9%B6%E5%8F%91%E7%B3%BB%E7%BB%9F%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何设计一个高并发系统？&lt;/h3&gt;
&lt;p&gt;可以分为以下 6 点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统拆分&lt;/li&gt;
&lt;li&gt;缓存&lt;/li&gt;
&lt;li&gt;MQ&lt;/li&gt;
&lt;li&gt;分库分表&lt;/li&gt;
&lt;li&gt;读写分离&lt;/li&gt;
&lt;li&gt;ElasticSearch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-b9e7f.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 77.60416666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAAsSAAALEgHS3X78AAABaklEQVQoz43T2W7CUAwE0Pz/JyKBBGInKWRhb6An1xKkUaXiB+RtxmPnkj17djgc1uv17XZ7fmbZALzdbu/3O//xeHwna9tW2CYTyr/BUQtrmqYsy/Crqtrv90IOOj5q4eVyCeoO/JXsdDoVRbHb7fI8123+ZrMJpN+6ruXhhb/AAIvFQmtwH49HyNlsdr1eCQGLE8AoyfSVZgCr1cqdFKbTKRYi9ZXJ6mSQsQIf6XtyeBEoY7EFejo5VbLz+WwpY4ayDVRALEBsi+CGp2i5XMLQErfU4Drwzt6BYxkAcdxJX6wkGSWMkhrIoQKXeYZnr28oCyxLbYBDG/B8Psfick5rsgyHkG7yaDSaTCZgUnSi10oCwfC6HbJJ5ivEzW1kRmZbR0IWLwEFCYbkySANjw2NQheKNHeyAUzTBBCXi+8R/mv/v982JNmG9B/z4GG/TtB/2B2Y+vF4HC90UPv/X+XCdibS8Njtc/sBlFeclPGQJ5oAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 22 00 55 45&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-fee1c.png&quot; data-srcset=&quot;/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-a67b7.png 200w,
/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-0b187.png 400w,
/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-fee1c.png 800w,
/static/2023-09-22-00-55-45-c9ae581949b705b17b5265f2037be59b-b9e7f.png 1152w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;系统拆分&quot;&gt;&lt;a href=&quot;#%E7%B3%BB%E7%BB%9F%E6%8B%86%E5%88%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;系统拆分&lt;/h4&gt;
&lt;p&gt;将一个系统拆分为多个子系统，用 dubbo 来搞。然后每个系统连一个数据库，这样本来就一个库，现在多个数据库，不也可以扛高并发么。&lt;/p&gt;
&lt;h4 id=&quot;缓存-1&quot;&gt;&lt;a href=&quot;#%E7%BC%93%E5%AD%98-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缓存&lt;/h4&gt;
&lt;p&gt;缓存，必须得用缓存。大部分的高并发场景，都是读多写少，那你完全可以在数据库和缓存里都写一份，然后读的时候大量走缓存不就得了。毕竟人家 redis 轻轻松松单机几万的并发。所以你可以考虑考虑你的项目里，那些承载主要请求的读场景，怎么用缓存来抗高并发。&lt;/p&gt;
&lt;h4 id=&quot;mq&quot;&gt;&lt;a href=&quot;#mq&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MQ&lt;/h4&gt;
&lt;p&gt;MQ，必须得用 MQ。可能你还是会出现高并发写的场景，比如说一个业务操作里要频繁搞数据库几十次，增删改增删改，疯了。那高并发绝对搞挂你的系统，你要是用 redis 来承载写那肯定不行，人家是缓存，数据随时就被 LRU 了，数据格式还无比简单，没有事务支持。所以该用 mysql 还得用 mysql 啊。那你咋办？用 MQ 吧，大量的写请求灌入 MQ 里，排队慢慢玩儿，后边系统消费后慢慢写，控制在 mysql 承载范围之内。所以你得考虑考虑你的项目里，那些承载复杂写业务逻辑的场景里，如何用 MQ 来异步写，提升并发性。MQ 单机抗几万并发也是 ok 的，这个之前还特意说过。&lt;/p&gt;
&lt;h4 id=&quot;分库分表-1&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分库分表&lt;/h4&gt;
&lt;p&gt;分库分表，可能到了最后数据库层面还是免不了抗高并发的要求，好吧，那么就将一个数据库拆分为多个库，多个库来扛更高的并发；然后将一个表拆分为多个表，每个表的数据量保持少一点，提高 sql 跑的性能。&lt;/p&gt;
&lt;h4 id=&quot;读写分离-1&quot;&gt;&lt;a href=&quot;#%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;读写分离&lt;/h4&gt;
&lt;p&gt;读写分离，这个就是说大部分时候数据库可能也是读多写少，没必要所有请求都集中在一个库上吧，可以搞个主从架构，主库写入，从库读取，搞一个读写分离。读流量太多的时候，还可以加更多的从库。&lt;/p&gt;
&lt;h4 id=&quot;elasticsearch&quot;&gt;&lt;a href=&quot;#elasticsearch&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ElasticSearch&lt;/h4&gt;
&lt;p&gt;Elasticsearch，简称 es。es 是分布式的，可以随便扩容，分布式天然就可以支撑高并发，因为动不动就可以扩容加机器来扛更高的并发。那么一些比较简单的查询、统计类的操作，可以考虑用 es 来承载，还有一些全文搜索类的操作，也可以考虑用 es 来承载。&lt;/p&gt;
&lt;p&gt;上面的 6 点，基本就是高并发系统肯定要干的一些事儿，大家可以仔细结合之前讲过的知识考虑一下，到时候你可以系统的把这块阐述一下，然后每个部分要注意哪些问题，之前都讲过了，你都可以阐述阐述，表明你对这块是有点积累的。&lt;/p&gt;
&lt;p&gt;说句实话，毕竟你真正厉害的一点，不是在于弄明白一些技术，或者大概知道一个高并发系统应该长什么样？其实实际上在真正的复杂的业务系统里，做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑：哪些需要分库分表，哪些不需要分库分表，单库单表跟分库分表如何 join，哪些数据要放到缓存里去，放哪些数据才可以扛住高并发的请求，你需要完成对一个复杂业务系统的分析之后，然后逐步逐步的加入高并发的系统架构的改造，这个过程是无比复杂的，一旦做过一次，并且做好了，你在这个市场上就会非常的吃香。&lt;/p&gt;
&lt;p&gt;其实大部分公司，真正看重的，不是说你掌握高并发相关的一些基本的架构知识，架构中的一些技术，RocketMQ、Kafka、Redis、Elasticsearch，高并发这一块，你了解了，也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统，一步一步架构、设计以及实践过高并发架构的人，这个经验是难能可贵的。&lt;/p&gt;
&lt;h1 id=&quot;分布式系统&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式系统&lt;/h1&gt;
&lt;p&gt;分布式业务系统，就是把原来用 Java 开发的一个大块系统，给拆分成多个子系统，多个子系统之间互相调用，形成一个大系统的整体。假设原来你做了一个 OA 系统，里面包含了权限模块、员工模块、请假模块、财务模块，一个工程，里面包含了一堆模块，模块与模块之间会互相去调用，1 台机器部署。现在如果你把这个系统给拆开，权限系统、员工系统、请假系统、财务系统 4 个系统，4 个工程，分别在 4 台机器上部署。一个请求过来，完成这个请求，这个员工系统，调用权限系统，调用请假系统，调用财务系统，4 个系统分别完成了一部分的事情，最后 4 个系统都干完了以后，才认为是这个请求已经完成了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;近几年开始兴起和流行 Spring Cloud，刚流行还没开始普及，目前普及的是 Dubbo，因此这里也主要讲 Dubbo。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;面试官可能会问你以下问题。&lt;/p&gt;
&lt;p&gt;为什么要进行系统拆分？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么要进行系统拆分？如何进行系统拆分？拆分后不用 Dubbo 可以吗？Dubbo 和 thrift 有什么区别呢？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分布式服务框架&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;说一下 Dubbo 的工作原理？注册中心挂了可以继续通信吗？&lt;/li&gt;
&lt;li&gt;Dubbo 支持哪些序列化协议？说一下 Hessian 的数据结构？PB 知道吗？为什么 PB 的效率是最高的？&lt;/li&gt;
&lt;li&gt;Dubbo 负载均衡策略和高可用策略都有哪些？动态代理策略呢？&lt;/li&gt;
&lt;li&gt;Dubbo 的 SPI 思想是什么？&lt;/li&gt;
&lt;li&gt;如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试？&lt;/li&gt;
&lt;li&gt;分布式服务接口的幂等性如何设计（比如不能重复扣款）？&lt;/li&gt;
&lt;li&gt;分布式服务接口请求的顺序性如何保证？&lt;/li&gt;
&lt;li&gt;如何自己设计一个类似 Dubbo 的 RPC 框架？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分布式锁&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 Redis 如何设计分布式锁？使用 zk 来设计分布式锁可以吗？这两种分布式锁的实现方式哪种效率比较高？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分布式事务&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分布式事务了解吗？你们如何解决分布式事务问题的？TCC 如果出现网络连不通怎么办？XA 的一致性如何保证？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分布式会话&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集群部署时的分布式 Session 如何实现？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;系统拆分-1&quot;&gt;&lt;a href=&quot;#%E7%B3%BB%E7%BB%9F%E6%8B%86%E5%88%86-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;系统拆分&lt;/h2&gt;
&lt;h3 id=&quot;为什么系统要拆分？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E4%BB%80%E4%B9%88%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%8B%86%E5%88%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为什么系统要拆分？&lt;/h3&gt;
&lt;p&gt;要是不拆分，一个大系统几十万行代码，20 个人维护一份代码，简直是悲剧啊。代码经常改着改着就冲突了，各种代码冲突和合并要处理，非常耗费时间；经常我改动了我的代码，你调用了我的，导致你的代码也得重新测试，麻烦的要死；然后每次发布都是几十万行代码的系统一起发布，大家得一起提心吊胆准备上线，几十万行代码的上线，可能每次上线都要做很多的检查，很多异常问题的处理，简直是又麻烦又痛苦；而且如果我现在打算把技术升级到最新的 spring 版本，还不行，因为这可能导致你的代码报错，我不敢随意乱改技术。&lt;/p&gt;
&lt;p&gt;假设一个系统是 20 万行代码，其中 A 在里面改了 1000 行代码，但是此时发布的时候是这个 20 万行代码的大系统一块儿发布。就意味着 20 万代码在线上就可能出现各种变化，20 个人，每个人都要紧张地等在电脑面前，上线之后，检查日志，看自己负责的那一块儿有没有什么问题。&lt;/p&gt;
&lt;p&gt;A 就检查了自己负责的 1 万行代码对应的功能，确保 ok 就闪人了；结果不巧的是，A 上线的时候不小心修改了线上机器的某个配置，导致另外 B 和 C 负责的 2 万行代码对应的一些功能，出错了。&lt;/p&gt;
&lt;p&gt;几十个人负责维护一个几十万行代码的单块应用，每次上线，准备几个礼拜，上线 -&gt; 部署 -&gt; 检查自己负责的功能。&lt;/p&gt;
&lt;p&gt;拆分了以后，整个世界清爽了，几十万行代码的系统，拆分成 20 个服务，平均每个服务就 1~2 万行代码，每个服务部署到单独的机器上。20 个工程，20 个 git 代码仓库，20 个开发人员，每个人维护自己的那个服务就可以了，是自己独立的代码，跟别人没关系。再也没有代码冲突了，爽。每次就测试我自己的代码就可以了，爽。每次就发布我自己的一个小服务就可以了，爽。技术上想怎么升级就怎么升级，保持接口不变就可以了，真爽。&lt;/p&gt;
&lt;p&gt;所以简单来说，一句话总结，如果是那种代码量多达几十万行的中大型项目，团队里有几十个人，那么如果不拆分系统，开发效率极其低下，问题很多。但是拆分系统之后，每个人就负责自己的一小部分就好了，可以随便玩儿随便弄。分布式系统拆分之后，可以大幅度提升复杂系统大型团队的开发效率。&lt;/p&gt;
&lt;p&gt;但是同时，也要提醒的一点是，系统拆分成分布式系统之后，大量的分布式系统面临的问题也是接踵而来，所以后面的问题都是在围绕分布式系统带来的复杂技术挑战在说。&lt;/p&gt;
&lt;h4 id=&quot;如何进行系统拆分？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E7%B3%BB%E7%BB%9F%E6%8B%86%E5%88%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何进行系统拆分？&lt;/h4&gt;
&lt;p&gt;这个问题说大可以很大，可以扯到领域驱动模型设计上去，说小了也很小，我不太想给大家太过于学术的说法，因为你也不可能背这个答案，过去了直接说吧。还是说的简单一点，大家自己到时候知道怎么回答就行了。&lt;/p&gt;
&lt;p&gt;系统拆分为分布式系统，拆成多个服务，拆成微服务的架构，是需要拆很多轮的。并不是说上来一个架构师一次就给拆好了，而以后都不用拆。&lt;/p&gt;
&lt;p&gt;第一轮；团队继续扩大，拆好的某个服务，刚开始是 1 个人维护 1 万行代码，后来业务系统越来越复杂，这个服务是 10 万行代码，5 个人；第二轮，1 个服务 -&gt; 5 个服务，每个服务 2 万行代码，每人负责一个服务。&lt;/p&gt;
&lt;p&gt;如果是多人维护一个服务，最理想的情况下，几十个人，1 个人负责 1 个或 2 ~ 3 个服务；某个服务工作量变大了，代码量越来越多，某个同学，负责一个服务，代码量变成了 10 万行了，他自己不堪重负，他现在一个人拆开，5 个服务，1 个人顶着，负责 5 个人，接着招人，2 个人，给那个同学带着，3 个人负责 5 个服务，其中 2 个人每个人负责 2 个服务，1 个人负责 1 个服务。&lt;/p&gt;
&lt;p&gt;个人建议，一个服务的代码不要太多，1 万行左右，两三万撑死了吧。&lt;/p&gt;
&lt;p&gt;大部分的系统，是要进行多轮拆分的，第一次拆分，可能就是将以前的多个模块该拆分开来了，比如说将电商系统拆分成订单系统、商品系统、采购系统、仓储系统、用户系统，等等吧。&lt;/p&gt;
&lt;p&gt;但是后面可能每个系统又变得越来越复杂了，比如说采购系统里面又分成了供应商管理系统、采购单管理系统，订单系统又拆分成了购物车系统、价格系统、订单管理系统。&lt;/p&gt;
&lt;p&gt;扯深了实在很深，所以这里先给大家举个例子，你自己感受一下，核心意思就是根据情况，先拆分一轮，后面如果系统更复杂了，可以继续分拆。你根据自己负责系统的例子，来考虑一下就好了。&lt;/p&gt;
&lt;h4 id=&quot;拆分后不用-dubbo-可以吗？&quot;&gt;&lt;a href=&quot;#%E6%8B%86%E5%88%86%E5%90%8E%E4%B8%8D%E7%94%A8-dubbo-%E5%8F%AF%E4%BB%A5%E5%90%97%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;拆分后不用 dubbo 可以吗？&lt;/h4&gt;
&lt;p&gt;当然可以了，大不了最次，就是各个系统之间，直接基于 spring mvc，就纯 http 接口互相通信呗，还能咋样。但是这个肯定是有问题的，因为 http 接口通信维护起来成本很高，你要考虑超时重试、负载均衡等等各种乱七八糟的问题，比如说你的订单系统调用商品系统，商品系统部署了 5 台机器，你怎么把请求均匀地甩给那 5 台机器？这不就是负载均衡？你要是都自己搞那是可以的，但是确实很痛苦。&lt;/p&gt;
&lt;p&gt;所以 dubbo 说白了，是一种 rpc 框架，就是说本地就是进行接口调用，但是 dubbo 会代理这个调用请求，跟远程机器网络通信，给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了，用 dubbo 就可以了。&lt;/p&gt;
&lt;h2 id=&quot;分布式服务框架&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E6%A1%86%E6%9E%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式服务框架&lt;/h2&gt;
&lt;h3 id=&quot;说一下-dubbo-的工作原理？&quot;&gt;&lt;a href=&quot;#%E8%AF%B4%E4%B8%80%E4%B8%8B-dubbo-%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;说一下 Dubbo 的工作原理？&lt;/h3&gt;
&lt;h4 id=&quot;dubbo-工作原理&quot;&gt;&lt;a href=&quot;#dubbo-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 工作原理&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;第一层：service 层，接口层，给服务提供者和消费者来实现的&lt;/li&gt;
&lt;li&gt;第二层：config 层，配置层，主要是对 dubbo 进行各种配置的&lt;/li&gt;
&lt;li&gt;第三层：proxy 层，服务代理层，无论是 consumer 还是 provider，dubbo 都会给你生成代理，代理之间进行网络通信&lt;/li&gt;
&lt;li&gt;第四层：registry 层，服务注册层，负责服务的注册与发现&lt;/li&gt;
&lt;li&gt;第五层：cluster 层，集群层，封装多个服务提供者的路由以及负载均衡，将多个实例组合成一个服务&lt;/li&gt;
&lt;li&gt;第六层：monitor 层，监控层，对 rpc 接口的调用次数和调用时间进行监控&lt;/li&gt;
&lt;li&gt;第七层：protocal 层，远程调用层，封装 rpc 调用&lt;/li&gt;
&lt;li&gt;第八层：exchange 层，信息交换层，封装请求响应模式，同步转异步&lt;/li&gt;
&lt;li&gt;第九层：transport 层，网络传输层，抽象 mina 和 netty 为统一接口&lt;/li&gt;
&lt;li&gt;第十层：serialize 层，数据序列化层&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;工作流程&quot;&gt;&lt;a href=&quot;#%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;工作流程&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;第一步：provider 向注册中心去注册&lt;/li&gt;
&lt;li&gt;第二步：consumer 从注册中心订阅服务，注册中心会通知 consumer 注册好的服务&lt;/li&gt;
&lt;li&gt;第三步：consumer 调用 provider&lt;/li&gt;
&lt;li&gt;第四步：consumer 和 provider 都异步通知监控中心&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;注册中心挂了可以继续通信吗？&quot;&gt;&lt;a href=&quot;#%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E6%8C%82%E4%BA%86%E5%8F%AF%E4%BB%A5%E7%BB%A7%E7%BB%AD%E9%80%9A%E4%BF%A1%E5%90%97%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;注册中心挂了可以继续通信吗？&lt;/h4&gt;
&lt;p&gt;可以，因为刚开始初始化的时候，消费者会将提供者的地址等信息拉取到本地缓存，所以注册中心挂了可以继续通信。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-支持哪些序列化协议？&quot;&gt;&lt;a href=&quot;#dubbo-%E6%94%AF%E6%8C%81%E5%93%AA%E4%BA%9B%E5%BA%8F%E5%88%97%E5%8C%96%E5%8D%8F%E8%AE%AE%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 支持哪些序列化协议？&lt;/h3&gt;
&lt;p&gt;序列化，就是把数据结构或者是一些对象，转换为二进制串的过程，而反序列化是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。&lt;/p&gt;
&lt;h4 id=&quot;dubbo-支持不同的通信协议&quot;&gt;&lt;a href=&quot;#dubbo-%E6%94%AF%E6%8C%81%E4%B8%8D%E5%90%8C%E7%9A%84%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 支持不同的通信协议&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;dubbo 协议 dubbo://&lt;/p&gt;
&lt;p&gt;默认就是走 dubbo 协议，单一长连接，进行的是 NIO 异步通信，基于 hessian 作为序列化协议。使用的场景是：传输数据量小（每次请求在 100kb 以内），但是并发量很高，以及服务消费者机器数远大于服务提供者机器数的情况。&lt;/p&gt;
&lt;p&gt;为了要支持高并发场景，一般是服务提供者就几台机器，但是服务消费者有上百台，可能每天调用量达到上亿次！此时用长连接是最合适的，就是跟每个服务消费者维持一个长连接就可以，可能总共就 100 个连接。然后后面直接基于长连接 NIO 异步通信，可以支撑高并发请求。&lt;/p&gt;
&lt;p&gt;长连接，通俗点说，就是建立连接过后可以持续发送请求，无须再建立连接。&lt;/p&gt;
&lt;p&gt;而短连接，每次要发送请求之前，需要先重新建立一次连接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rmi 协议 rmi://&lt;/p&gt;
&lt;p&gt;RMI 协议采用 JDK 标准的 &lt;code class=&quot;language-text&quot;&gt;java.rmi.*&lt;/code&gt; 实现，采用阻塞式短连接和 JDK 标准序列化方式。多个短连接，适合消费者和提供者数量差不多的情况，适用于文件的传输，一般较少用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hessian 协议 hessian://&lt;/p&gt;
&lt;p&gt;Hessian 1 协议用于集成 Hessian 的服务，Hessian 底层采用 Http 通讯，采用 Servlet 暴露服务，Dubbo 缺省内嵌 Jetty 作为服务器实现。走 hessian 序列化协议，多个短连接，适用于提供者数量比消费者数量还多的情况，适用于文件的传输，一般较少用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;http 协议 http://&lt;/p&gt;
&lt;p&gt;基于 HTTP 表单的远程调用协议，采用 Spring 的 HttpInvoker 实现。走表单序列化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;thrift 协议 thrift://&lt;/p&gt;
&lt;p&gt;当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展，在原生协议的基础上添加了一些额外的头信息，比如 service name，magic number 等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;webservice webservice://&lt;/p&gt;
&lt;p&gt;基于 WebService 的远程调用协议，基于 Apache CXF 的 frontend-simple 和 transports-http 实现。走 SOAP 文本序列化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;memcached 协议 memcached://&lt;/p&gt;
&lt;p&gt;基于 memcached 实现的 RPC 协议。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;redis 协议 redis://&lt;/p&gt;
&lt;p&gt;基于 Redis 实现的 RPC 协议。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rest 协议 rest://&lt;/p&gt;
&lt;p&gt;基于标准的 Java REST API —— JAX-RS 2.0（Java API for RESTful Web Services 的简写）实现的 REST 调用支持。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;gPRC 协议 grpc://&lt;/p&gt;
&lt;p&gt;Dubbo 自 2.7.5 版本开始支持 gRPC 协议，对于计划使用 HTTP/2 通信，或者想利用 gRPC 带来的 Stream、反压、Reactive 编程等能力的开发者来说，都可以考虑启用 gRPC 协议。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;dubbo-支持的序列化协议&quot;&gt;&lt;a href=&quot;#dubbo-%E6%94%AF%E6%8C%81%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E5%8D%8F%E8%AE%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 支持的序列化协议&lt;/h4&gt;
&lt;p&gt;dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议。但是 hessian 是其默认的序列化协议。&lt;/p&gt;
&lt;h4 id=&quot;说一下-hessian-的数据结构&quot;&gt;&lt;a href=&quot;#%E8%AF%B4%E4%B8%80%E4%B8%8B-hessian-%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;说一下 Hessian 的数据结构&lt;/h4&gt;
&lt;p&gt;Hessian 的对象序列化机制有 8 种原始类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原始二进制数据&lt;/li&gt;
&lt;li&gt;boolean&lt;/li&gt;
&lt;li&gt;64-bit date（64 位毫秒值的日期）&lt;/li&gt;
&lt;li&gt;64-bit double&lt;/li&gt;
&lt;li&gt;32-bit int&lt;/li&gt;
&lt;li&gt;64-bit long&lt;/li&gt;
&lt;li&gt;null&lt;/li&gt;
&lt;li&gt;UTF-8 编码的 string&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外还包括 3 种递归类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;list for lists and arrays&lt;/li&gt;
&lt;li&gt;map for maps and dictionaries&lt;/li&gt;
&lt;li&gt;object for objects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有一种特殊的类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ref：用来表示对共享对象的引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;为什么-pb-的效率是最高的？&quot;&gt;&lt;a href=&quot;#%E4%B8%BA%E4%BB%80%E4%B9%88-pb-%E7%9A%84%E6%95%88%E7%8E%87%E6%98%AF%E6%9C%80%E9%AB%98%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;为什么 PB 的效率是最高的？&lt;/h4&gt;
&lt;p&gt;其实 PB 之所以性能如此好，主要得益于两个：第一，它使用 proto 编译器，自动进行序列化和反序列化，速度非常快，应该比 XML 和 JSON 快上了 20 ~ 100 倍；第二，它的数据压缩效果好，就是说它序列化后的数据量体积小。因为体积小，传输起来带宽和速度上会有优化。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-负载均衡策略和集群容错策略？&quot;&gt;&lt;a href=&quot;#dubbo-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5%E5%92%8C%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E7%AD%96%E7%95%A5%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 负载均衡策略和集群容错策略？&lt;/h3&gt;
&lt;h4 id=&quot;dubbo-负载均衡策略&quot;&gt;&lt;a href=&quot;#dubbo-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 负载均衡策略&lt;/h4&gt;
&lt;h5 id=&quot;randomloadbalance&quot;&gt;&lt;a href=&quot;#randomloadbalance&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RandomLoadBalance&lt;/h5&gt;
&lt;p&gt;默认情况下，dubbo 是 RandomLoadBalance，即随机调用实现负载均衡，可以对 provider 不同实例设置不同的权重，会按照权重来负载均衡，权重越大分配流量越高，一般就用这个默认的就可以了。&lt;/p&gt;
&lt;p&gt;算法思想很简单。假设有一组服务器 servers = &lt;code class=&quot;language-text&quot;&gt;[A, B, C]&lt;/code&gt;，他们对应的权重为 weights = &lt;code class=&quot;language-text&quot;&gt;[5, 3, 2]&lt;/code&gt;，权重总和为 10。现在把这些权重值平铺在一维坐标值上，&lt;code class=&quot;language-text&quot;&gt;[0, 5)&lt;/code&gt; 区间属于服务器 A，&lt;code class=&quot;language-text&quot;&gt;[5, 8)&lt;/code&gt; 区间属于服务器 B，&lt;code class=&quot;language-text&quot;&gt;[8, 10)&lt;/code&gt; 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 &lt;code class=&quot;language-text&quot;&gt;[0, 10)&lt;/code&gt; 之间的随机数，然后计算这个随机数会落到哪个区间上。比如数字 3 会落到服务器 A 对应的区间上，此时返回服务器 A 即可。权重越大的机器，在坐标轴上对应的区间范围就越大，因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好，在经过多次选择后，每个服务器被选中的次数比例接近其权重比例。比如，经过一万次选择后，服务器 A 被选中的次数大约为 5000 次，服务器 B 被选中的次数约为 3000 次，服务器 C 被选中的次数约为 2000 次。&lt;/p&gt;
&lt;h5 id=&quot;roundrobinloadbalance&quot;&gt;&lt;a href=&quot;#roundrobinloadbalance&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RoundRobinLoadBalance&lt;/h5&gt;
&lt;p&gt;这个的话默认就是均匀地将流量打到各个机器上去，但是如果各个机器的性能不一样，容易导致性能差的机器负载过高。所以此时需要调整权重，让性能差的机器承载权重小一些，流量少一些。&lt;/p&gt;
&lt;p&gt;举个栗子。&lt;/p&gt;
&lt;p&gt;跟运维同学申请机器，有的时候，我们运气好，正好公司资源比较充足，刚刚有一批热气腾腾、刚刚做好的虚拟机新鲜出炉，配置都比较高：8 核 + 16G 机器，申请到 2 台。过了一段时间，我们感觉 2 台机器有点不太够，我就去找运维同学说，“哥儿们，你能不能再给我一台机器”，但是这时只剩下一台 4 核 + 8G 的机器。我要还是得要。&lt;/p&gt;
&lt;p&gt;这个时候，可以给两台 8 核 16G 的机器设置权重 4，给剩余 1 台 4 核 8G 的机器设置权重 2。&lt;/p&gt;
&lt;h5 id=&quot;leastactiveloadbalance&quot;&gt;&lt;a href=&quot;#leastactiveloadbalance&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;LeastActiveLoadBalance&lt;/h5&gt;
&lt;p&gt;官网对 LeastActiveLoadBalance 的解释是“最小活跃数负载均衡”，活跃调用数越小，表明该服务提供者效率越高，单位时间内可处理更多的请求，那么此时请求会优先分配给该服务提供者。&lt;/p&gt;
&lt;p&gt;最小活跃数负载均衡算法的基本思想是这样的：&lt;/p&gt;
&lt;p&gt;每个服务提供者会对应着一个活跃数 active。初始情况下，所有服务提供者的 active 均为 0。每当收到一个请求，对应的服务提供者的 active 会加 1，处理完请求后，active 会减 1。所以，如果服务提供者性能较好，处理请求的效率就越高，那么 active 也会下降的越快。因此可以给这样的服务提供者优先分配请求。&lt;/p&gt;
&lt;p&gt;当然，除了最小活跃数，LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说，LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。&lt;/p&gt;
&lt;h5 id=&quot;consistenthashloadbalance&quot;&gt;&lt;a href=&quot;#consistenthashloadbalance&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ConsistentHashLoadBalance&lt;/h5&gt;
&lt;p&gt;一致性 Hash 算法，相同参数的请求一定分发到一个 provider 上去，provider 挂掉的时候，会基于虚拟节点均匀分配剩余的流量，抖动不会太大。如果你需要的不是随机负载均衡，是要一类请求都到一个节点，那就走这个一致性 Hash 策略。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关于 dubbo 负载均衡策略更加详细的描述，可以查看官网 &lt;a href=&quot;https://dubbo.apache.org/zh/docs/advanced/loadbalance&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://dubbo.apache.org/zh/docs/advanced/loadbalance&lt;/a&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;dubbo-集群容错策略&quot;&gt;&lt;a href=&quot;#dubbo-%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 集群容错策略&lt;/h4&gt;
&lt;h5 id=&quot;failover-cluster-模式&quot;&gt;&lt;a href=&quot;#failover-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Failover Cluster 模式&lt;/h5&gt;
&lt;p&gt;失败自动切换，自动重试其他机器，默认就是这个，常见于读操作。（失败重试其它机器）&lt;/p&gt;
&lt;p&gt;可以通过以下几种方式配置重试次数：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;97641954176237910000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:service retries=&amp;quot;2&amp;quot; /&gt;`, `97641954176237910000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;service&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;5909728523767610000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference retries=&amp;quot;2&amp;quot; /&gt;`, `5909728523767610000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;42622883261259320000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference&gt;
    &lt;dubbo:method name=&amp;quot;findFoo&amp;quot; retries=&amp;quot;2&amp;quot; /&gt;
&lt;/dubbo:reference&gt;`, `42622883261259320000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;method&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;findFoo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;failfast-cluster-模式&quot;&gt;&lt;a href=&quot;#failfast-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Failfast Cluster 模式&lt;/h5&gt;
&lt;p&gt;一次调用失败就立即失败，常见于非幂等性的写操作，比如新增一条记录（调用失败就立即失败）&lt;/p&gt;
&lt;h5 id=&quot;failsafe-cluster-模式&quot;&gt;&lt;a href=&quot;#failsafe-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Failsafe Cluster 模式&lt;/h5&gt;
&lt;p&gt;出现异常时忽略掉，常用于不重要的接口调用，比如记录日志。&lt;/p&gt;
&lt;p&gt;配置示例如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17421302787496872000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:service cluster=&amp;quot;failsafe&amp;quot; /&gt;`, `17421302787496872000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;service&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;cluster&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;failsafe&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;8308921149409198000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference cluster=&amp;quot;failsafe&amp;quot; /&gt;`, `8308921149409198000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;cluster&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;failsafe&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;failback-cluster-模式&quot;&gt;&lt;a href=&quot;#failback-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Failback Cluster 模式&lt;/h5&gt;
&lt;p&gt;失败了后台自动记录请求，然后定时重发，比较适合于写消息队列这种。&lt;/p&gt;
&lt;h5 id=&quot;forking-cluster-模式&quot;&gt;&lt;a href=&quot;#forking-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Forking Cluster 模式&lt;/h5&gt;
&lt;p&gt;并行调用多个 provider，只要一个成功就立即返回。常用于实时性要求比较高的读操作，但是会浪费更多的服务资源，可通过 &lt;code class=&quot;language-text&quot;&gt;forks=&amp;quot;2&amp;quot;&lt;/code&gt; 来设置最大并行数。&lt;/p&gt;
&lt;h5 id=&quot;broadcast-cluster-模式&quot;&gt;&lt;a href=&quot;#broadcast-cluster-%E6%A8%A1%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Broadcast Cluster 模式&lt;/h5&gt;
&lt;p&gt;逐个调用所有的 provider。任何一个 provider 出错则报错（从 2.1.0 版本开始支持）。通常用于通知所有提供者更新缓存或日志等本地资源信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关于 dubbo 集群容错策略更加详细的描述，可以查看官网 &lt;a href=&quot;https://dubbo.apache.org/zh/docs/advanced/fault-tolerent-strategy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://dubbo.apache.org/zh/docs/advanced/fault-tolerent-strategy&lt;/a&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;dubbo-动态代理策略&quot;&gt;&lt;a href=&quot;#dubbo-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E7%AD%96%E7%95%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 动态代理策略&lt;/h4&gt;
&lt;p&gt;默认使用 javassist 动态字节码生成，创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。&lt;/p&gt;
&lt;h3 id=&quot;dubbo-的-spi-思想是什么？&quot;&gt;&lt;a href=&quot;#dubbo-%E7%9A%84-spi-%E6%80%9D%E6%83%B3%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dubbo 的 SPI 思想是什么？&lt;/h3&gt;
&lt;h4 id=&quot;spi-是啥？&quot;&gt;&lt;a href=&quot;#spi-%E6%98%AF%E5%95%A5%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;spi 是啥？&lt;/h4&gt;
&lt;p&gt;spi，简单来说，就是 service provider interface，说白了是什么意思呢，比如你有个接口，现在这个接口有 3 个实现类，那么在系统运行的时候对这个接口到底选择哪个实现类呢？这就需要 spi 了，需要根据指定的配置或者是默认的配置，去找到对应的实现类加载进来，然后用这个实现类的实例对象。&lt;/p&gt;
&lt;p&gt;举个栗子。&lt;/p&gt;
&lt;p&gt;你有一个接口 A。A1 / A2 / A3 分别是接口 A 的不同实现。你通过配置接口 A = 实现 A2 ，那么在系统实际运行的时候，会加载你的配置，用实现 A2 实例化一个对象来提供服务。&lt;/p&gt;
&lt;p&gt;spi 机制一般用在哪儿？插件扩展的场景，比如说你开发了一个给别人使用的开源框架，如果你想让别人自己写个插件，插到你的开源框架里面，从而扩展某个功能，这个时候 spi 思想就用上了。&lt;/p&gt;
&lt;h4 id=&quot;java-spi-思想的体现&quot;&gt;&lt;a href=&quot;#java-spi-%E6%80%9D%E6%83%B3%E7%9A%84%E4%BD%93%E7%8E%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Java spi 思想的体现&lt;/h4&gt;
&lt;p&gt;spi 经典的思想体现，大家平时都在用，比如说 jdbc。&lt;/p&gt;
&lt;p&gt;Java 定义了一套 jdbc 的接口，但是 Java 并没有提供 jdbc 的实现类。&lt;/p&gt;
&lt;p&gt;但是实际上项目跑的时候，要使用 jdbc 接口的哪些实现类呢？一般来说，我们要根据自己使用的数据库，比如 mysql，你就将 &lt;code class=&quot;language-text&quot;&gt;mysql-jdbc-connector.jar&lt;/code&gt; 引入进来；oracle，你就将 &lt;code class=&quot;language-text&quot;&gt;oracle-jdbc-connector.jar&lt;/code&gt; 引入进来。&lt;/p&gt;
&lt;p&gt;在系统跑的时候，碰到你使用 jdbc 的接口，他会在底层使用你引入的那个 jar 中提供的实现类。&lt;/p&gt;
&lt;h4 id=&quot;dubbo-的-spi-思想&quot;&gt;&lt;a href=&quot;#dubbo-%E7%9A%84-spi-%E6%80%9D%E6%83%B3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dubbo 的 spi 思想&lt;/h4&gt;
&lt;p&gt;dubbo 也用了 spi 思想，不过没有用 jdk 的 spi 机制，是自己实现的一套 spi 机制。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;75577404188486780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();`, `75577404188486780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; protocol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExtensionLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAdaptiveExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Protocol 接口，在系统运行的时候，dubbo 会判断一下应该选用这个 Protocol 接口的哪个实现类来实例化对象来使用。&lt;/p&gt;
&lt;p&gt;它会去找一个你配置的 Protocol，将你配置的 Protocol 实现类，加载到 jvm 中来，然后实例化对象，就用你的那个 Protocol 实现类就可以了。&lt;/p&gt;
&lt;p&gt;上面那行代码就是 dubbo 里大量使用的，就是对很多组件，都是保留一个接口和多个实现，然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置，那就走默认的实现好了，没问题。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62835175843280850000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SPI(&amp;quot;dubbo&amp;quot;)
public interface Protocol {
    int getDefaultPort();

    @Adaptive
    &lt;T&gt; Exporter&lt;T&gt; export(Invoker&lt;T&gt; invoker) throws RpcException;

    @Adaptive
    &lt;T&gt; Invoker&lt;T&gt; refer(Class&lt;T&gt; type, URL url) throws RpcException;

    void destroy();
}`, `62835175843280850000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SPI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dubbo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Protocol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDefaultPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exporter&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; invoker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Adaptive&lt;/span&gt;
    &lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Invoker&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;refer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RpcException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 dubbo 自己的 jar 里，在 &lt;code class=&quot;language-text&quot;&gt;/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol&lt;/code&gt; 文件中：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62192192990413160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol`, `62192192990413160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所以说，这就看到了 dubbo 的 spi 机制默认是怎么玩儿的了，其实就是 Protocol 接口，&lt;code class=&quot;language-text&quot;&gt;@SPI(&amp;quot;dubbo&amp;quot;)&lt;/code&gt; 说的是，通过 SPI 机制来提供实现类，实现类是通过 dubbo 作为默认 key 去配置文件里找到的，配置文件名称与接口全限定名一样的，通过 dubbo 作为 key 可以找到默认的实现类就是 com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。&lt;/p&gt;
&lt;p&gt;如果想要动态替换掉默认的实现类，需要使用 &lt;code class=&quot;language-text&quot;&gt;@Adaptive&lt;/code&gt; 接口，Protocol 接口中，有两个方法加了 &lt;code class=&quot;language-text&quot;&gt;@Adaptive&lt;/code&gt; 注解，就是说那俩接口会被代理实现。&lt;/p&gt;
&lt;p&gt;啥意思呢？&lt;/p&gt;
&lt;p&gt;比如这个 Protocol 接口搞了俩 &lt;code class=&quot;language-text&quot;&gt;@Adaptive&lt;/code&gt; 注解标注了方法，在运行的时候会针对 Protocol 生成代理类，这个代理类的那俩方法里面会有代理代码，代理代码会在运行的时候动态根据 url 中的 protocol 来获取那个 key，默认是 dubbo，你也可以自己指定，你如果指定了别的 key，那么就会获取别的实现类的实例了。&lt;/p&gt;
&lt;h4 id=&quot;如何自己扩展-dubbo-中的组件&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%87%AA%E5%B7%B1%E6%89%A9%E5%B1%95-dubbo-%E4%B8%AD%E7%9A%84%E7%BB%84%E4%BB%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何自己扩展 dubbo 中的组件&lt;/h4&gt;
&lt;p&gt;下面来说说怎么来自己扩展 dubbo 中的组件。&lt;/p&gt;
&lt;p&gt;自己写个工程，要是那种可以打成 jar 包的，里面的 &lt;code class=&quot;language-text&quot;&gt;src/main/resources&lt;/code&gt; 目录下，搞一个 &lt;code class=&quot;language-text&quot;&gt;META-INF/services&lt;/code&gt;，里面放个文件叫：&lt;code class=&quot;language-text&quot;&gt;com.alibaba.dubbo.rpc.Protocol&lt;/code&gt;，文件里搞一个 &lt;code class=&quot;language-text&quot;&gt;my=com.bingo.MyProtocol&lt;/code&gt;。自己把 jar 弄到 nexus 私服里去。&lt;/p&gt;
&lt;p&gt;然后自己搞一个 dubbo provider 工程，在这个工程里面依赖你自己搞的那个 jar，然后在 spring 配置文件里给个配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;47003359286891040000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:protocol name=&amp;quot;my&amp;quot; port=&amp;quot;20000&amp;quot; /&gt;`, `47003359286891040000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;protocol&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;20000&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;provider 启动的时候，就会加载到我们 jar 包里的 &lt;code class=&quot;language-text&quot;&gt;my=com.bingo.MyProtocol&lt;/code&gt; 这行配置里，接着会根据你的配置使用你定义好的 MyProtocol 了，这个就是简单说明一下，你通过上述方式，可以替换掉大量的 dubbo 内部的组件，就是扔个你自己的 jar 包，然后配置一下即可。&lt;/p&gt;
&lt;p&gt;dubbo 里面提供了大量的类似上面的扩展点，就是说，你如果要扩展一个东西，只要自己写个 jar，让你的 consumer 或者是 provider 工程，依赖你的那个 jar，在你的 jar 里指定目录下配置好接口名称对应的文件，里面通过 key = 实现类 。&lt;/p&gt;
&lt;p&gt;然后对于对应的组件，类似 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;dubbo:protocol&amp;gt;&lt;/code&gt; 用你的那个 key 对应的实现类来实现某个接口，你可以自己去扩展 dubbo 的各种功能，提供你自己的实现。&lt;/p&gt;
&lt;h3 id=&quot;如何基于-dubbo-进行服务治理？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8E-dubbo-%E8%BF%9B%E8%A1%8C%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何基于 Dubbo 进行服务治理？&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;h4 id=&quot;服务治理&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务治理&lt;/h4&gt;
&lt;h5 id=&quot;调用链路自动生成&quot;&gt;&lt;a href=&quot;#%E8%B0%83%E7%94%A8%E9%93%BE%E8%B7%AF%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;调用链路自动生成&lt;/h5&gt;
&lt;p&gt;一个大型的分布式系统，或者说是用现在流行的微服务架构来说吧，分布式系统由大量的服务组成。那么这些服务之间互相是如何调用的？调用链路是啥？说实话，几乎到后面没人搞的清楚了，因为服务实在太多了，可能几百个甚至几千个服务。&lt;/p&gt;
&lt;p&gt;那就需要基于 dubbo 做的分布式系统中，对各个服务之间的调用自动记录下来，然后自动将各个服务之间的依赖关系和调用链路生成出来，做成一张图，显示出来，大家才可以看到对吧。&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;---
title: 服务间调用链路
---
flowchart LR
    A[服务 A] --&gt; B[服务 B]
    A[服务 A] --&gt; C[服务 C]
    A[服务 A] --&gt; D[服务 D]
    B[服务 B] --&gt; E[服务 E]
    B[服务 B] --&gt; F[服务 F]
    C[服务 C] --&gt; G[服务 G]
    D[服务 D] --&gt; G[服务 G]&lt;/div&gt;
&lt;h5 id=&quot;服务访问压力以及时长统计&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E8%AE%BF%E9%97%AE%E5%8E%8B%E5%8A%9B%E4%BB%A5%E5%8F%8A%E6%97%B6%E9%95%BF%E7%BB%9F%E8%AE%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务访问压力以及时长统计&lt;/h5&gt;
&lt;p&gt;需要自动统计各个接口和服务之间的调用次数以及访问延时，而且要分成两个级别。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个级别是接口粒度，就是每个服务的每个接口每天被调用多少次，TP50/TP90/TP99，三个档次的请求延时分别是多少；&lt;/li&gt;
&lt;li&gt;第二个级别是从源头入口开始，一个完整的请求链路经过几十个服务之后，完成一次请求，每天全链路走多少次，全链路请求延时的 TP50/TP90/TP99，分别是多少。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些东西都搞定了之后，后面才可以来看当前系统的压力主要在哪里，如何来扩容和优化。&lt;/p&gt;
&lt;h5 id=&quot;其它&quot;&gt;&lt;a href=&quot;#%E5%85%B6%E5%AE%83&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;其它&lt;/h5&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;每个服务的可用性的监控（接口调用成功率？几个 9？99.99%，99.9%，99%）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;服务降级&quot;&gt;&lt;a href=&quot;#%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;服务降级&lt;/h4&gt;
&lt;p&gt;比如说服务 A 调用服务 B，结果服务 B 挂掉了，服务 A 重试几次调用服务 B，还是不行，那么直接降级，走一个备用的逻辑，给用户返回响应。&lt;/p&gt;
&lt;p&gt;举个栗子，我们有接口 HelloService。HelloServiceImpl 有该接口的具体实现。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62373283871679660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public interface HelloService {
   void sayHello();
}

public class HelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println(&amp;quot;hello world......&amp;quot;);
    }
}`, `62373283871679660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloServiceImpl&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello world......&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70901378041805455000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&gt;
&lt;beans xmlns=&amp;quot;http://www.springframework.org/schema/beans&amp;quot;
    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xmlns:dubbo=&amp;quot;http://code.alibabatech.com/schema/dubbo&amp;quot;
    xsi:schemaLocation=&amp;quot;http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd&amp;quot;&gt;

    &lt;dubbo:application name=&amp;quot;dubbo-provider&amp;quot; /&gt;
    &lt;dubbo:registry address=&amp;quot;zookeeper://127.0.0.1:2181&amp;quot; /&gt;
    &lt;dubbo:protocol name=&amp;quot;dubbo&amp;quot; port=&amp;quot;20880&amp;quot; /&gt;
    &lt;dubbo:service interface=&amp;quot;com.zhss.service.HelloService&amp;quot; ref=&amp;quot;helloServiceImpl&amp;quot; timeout=&amp;quot;10000&amp;quot; /&gt;
    &lt;bean id=&amp;quot;helloServiceImpl&amp;quot; class=&amp;quot;com.zhss.service.HelloServiceImpl&amp;quot; /&gt;

&lt;/beans&gt;

&lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&gt;
&lt;beans xmlns=&amp;quot;http://www.springframework.org/schema/beans&amp;quot;
    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;
    xmlns:dubbo=&amp;quot;http://code.alibabatech.com/schema/dubbo&amp;quot;
    xsi:schemaLocation=&amp;quot;http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd&amp;quot;&gt;

    &lt;dubbo:application name=&amp;quot;dubbo-consumer&amp;quot;  /&gt;

    &lt;dubbo:registry address=&amp;quot;zookeeper://127.0.0.1:2181&amp;quot; /&gt;

    &lt;dubbo:reference id=&amp;quot;fooService&amp;quot; interface=&amp;quot;com.test.service.FooService&amp;quot;  timeout=&amp;quot;10000&amp;quot; check=&amp;quot;false&amp;quot; mock=&amp;quot;return null&amp;quot;&gt;
    &lt;/dubbo:reference&gt;

&lt;/beans&gt;`, `70901378041805455000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;beans&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.springframework.org/schema/beans&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsi&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2001/XMLSchema-instance&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;dubbo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://code.alibabatech.com/schema/dubbo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xsi:&lt;/span&gt;schemaLocation&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;application&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;dubbo-provider&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;registry&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;zookeeper://127.0.0.1:2181&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;protocol&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;dubbo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;20880&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;service&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.zhss.service.HelloService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;helloServiceImpl&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10000&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;bean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;helloServiceImpl&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.zhss.service.HelloServiceImpl&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;beans&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;beans&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.springframework.org/schema/beans&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsi&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2001/XMLSchema-instance&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;dubbo&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://code.alibabatech.com/schema/dubbo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xsi:&lt;/span&gt;schemaLocation&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;application&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;dubbo-consumer&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;registry&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;zookeeper://127.0.0.1:2181&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;fooService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.test.service.FooService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;  &lt;span class=&quot;token attr-name&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10000&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;return null&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;beans&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们调用接口失败的时候，可以通过 mock 统一返回 null。&lt;/p&gt;
&lt;p&gt;mock 的值也可以修改为 true，然后再跟接口同一个路径下实现一个 Mock 类，命名规则是 &lt;code class=&quot;language-text&quot;&gt;接口名称 + Mock&lt;/code&gt; 后缀。然后在 Mock 类里实现自己的降级逻辑。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;67391679924883685000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class HelloServiceMock implements HelloService {
    public void sayHello() {
        // 降级逻辑
    }
}`, `67391679924883685000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloServiceMock&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 降级逻辑&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;失败重试和超时重试&quot;&gt;&lt;a href=&quot;#%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95%E5%92%8C%E8%B6%85%E6%97%B6%E9%87%8D%E8%AF%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;失败重试和超时重试&lt;/h4&gt;
&lt;p&gt;所谓失败重试，就是 consumer 调用 provider 要是失败了，比如抛异常了，此时应该是可以重试的，或者调用超时了也可以重试。配置如下：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29235214508979413000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dubbo:reference id=&amp;quot;xxxx&amp;quot; interface=&amp;quot;xx&amp;quot; check=&amp;quot;true&amp;quot; async=&amp;quot;false&amp;quot; retries=&amp;quot;3&amp;quot; timeout=&amp;quot;2000&amp;quot;/&gt;`, `29235214508979413000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;dubbo:&lt;/span&gt;reference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;xxxx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;xx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;3&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2000&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;举个栗子。&lt;/p&gt;
&lt;p&gt;某个服务的接口，要耗费 5s，你这边不能干等着，你这边配置了 timeout 之后，我等待 2s，还没返回，我直接就撤了，不能干等你。&lt;/p&gt;
&lt;p&gt;可以结合你们公司具体的场景来说说你是怎么设置这些参数的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;timeout：一般设置为 200ms，我们认为不能超过 200ms 还没返回。&lt;/li&gt;
&lt;li&gt;retries：设置 retries，一般是在读请求的时候，比如你要查询个数据，你可以设置个 retries，如果第一次没读到，报错，重试指定的次数，尝试再次读取。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;分布式服务接口的幂等性如何设计？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%B9%82%E7%AD%89%E6%80%A7%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式服务接口的幂等性如何设计？&lt;/h3&gt;
&lt;p&gt;一个分布式系统中的某个接口，该如何保证幂等性？这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢？&lt;/p&gt;
&lt;p&gt;假如你有个服务提供一些接口供外部调用，这个服务部署在了 5 台机器上，接着有个接口就是付款接口。然后人家用户在前端上操作的时候，不知道为啥，总之就是一个订单不小心发起了两次支付请求，然后这俩请求分散在了这个服务部署的不同的机器上，好了，结果一个订单扣款扣两次。&lt;/p&gt;
&lt;p&gt;或者是订单系统调用支付系统进行支付，结果不小心因为网络超时了，然后订单系统走了前面我们看到的那个重试机制，咔嚓给你重试了一把，好，支付系统收到一个支付请求两次，而且因为负载均衡算法落在了不同的机器上，尴尬了。。。&lt;/p&gt;
&lt;p&gt;所以你肯定得知道这事儿，否则你做出来的分布式系统恐怕容易埋坑。&lt;/p&gt;
&lt;p&gt;这个不是技术问题，这个没有通用的一个方法，这个应该结合业务来保证幂等性。&lt;/p&gt;
&lt;p&gt;所谓幂等性，就是说一个接口，多次发起同一个请求，你这个接口得保证结果是准确的，比如不能多扣款、不能多插入一条数据、不能将统计值多加了 1。这就是幂等性。&lt;/p&gt;
&lt;p&gt;其实保证幂等性主要是三点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于每个请求必须有一个唯一的标识，举个栗子：订单支付请求，肯定得包含订单 id，一个订单 id 最多支付一次，对吧。&lt;/li&gt;
&lt;li&gt;每次处理完请求之后，必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的，比如支付之前记录一条这个订单的支付流水。&lt;/li&gt;
&lt;li&gt;每次接收请求需要进行判断，判断之前是否处理过。比如说，如果有一个订单已经支付了，就已经有了一条支付流水，那么如果重复发送这个请求，则此时先插入支付流水，orderId 已经存在了，唯一键约束生效，报错插入不进去的。然后你就不用再扣款了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际运作过程中，你要结合自己的业务来，比如说利用 Redis，用 orderId 作为唯一键。只有成功插入这个支付流水，才可以执行实际的支付扣款。&lt;/p&gt;
&lt;p&gt;要求是支付一个订单，必须插入一条支付流水，&lt;code class=&quot;language-text&quot;&gt;order_id&lt;/code&gt; 建一个唯一键 unique key。你在支付一个订单之前，先插入一条支付流水，&lt;code class=&quot;language-text&quot;&gt;order_id&lt;/code&gt; 就已经进去了。你就可以写一个标识到 Redis 里面去，set &lt;code class=&quot;language-text&quot;&gt;order_id&lt;/code&gt; payed ，下一次重复请求过来了，先查 Redis 的 &lt;code class=&quot;language-text&quot;&gt;order_id&lt;/code&gt; 对应的 value，如果是 payed 就说明已经支付过了，你就别重复支付了。&lt;/p&gt;
&lt;h3 id=&quot;分布式服务接口请求的顺序性如何保证？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E6%9C%8D%E5%8A%A1%E6%8E%A5%E5%8F%A3%E8%AF%B7%E6%B1%82%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%80%A7%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式服务接口请求的顺序性如何保证？&lt;/h3&gt;
&lt;p&gt;其实分布式系统接口的调用顺序，也是个问题，一般来说是不用保证顺序的。但是有时候可能确实是需要严格的顺序保证。给大家举个例子，你服务 A 调用服务 B，先插入再删除。好，结果俩请求过去了，落在不同机器上，可能插入请求因为某些原因执行慢了一些，导致删除请求先执行了，此时因为没数据所以啥效果也没有；结果这个时候插入请求过来了，好，数据插入进去了，那就尴尬了。&lt;/p&gt;
&lt;p&gt;本来应该是 “先插入 -&gt; 再删除”，这条数据应该没了，结果现在 “先删除 -&gt; 再插入”，数据还存在，最后你死都想不明白是怎么回事。&lt;/p&gt;
&lt;p&gt;所以这都是分布式系统一些很常见的问题。&lt;/p&gt;
&lt;p&gt;首先，一般来说，个人建议是，你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证，因为一旦引入顺序性保障，比如使用分布式锁，会导致系统复杂度上升，而且会带来效率低下，热点数据压力过大等问题。&lt;/p&gt;
&lt;p&gt;下面我给个我们用过的方案吧，简单来说，首先你得用 Dubbo 的一致性 hash 负载均衡策略，将比如某一个订单 id 对应的请求都给分发到某个机器上去，接着就是在那个机器上，因为可能还是多线程并发执行的，你可能得立即将某个订单 id 对应的请求扔一个内存队列里去，强制排队，这样来确保他们的顺序性。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-c7bf1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 122.88732394366197%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAAsSAAALEgHS3X78AAAB+klEQVQ4y43Ux47iUBAFUP//p7FhAwsyiJxzGgZ6TrtaBnmM6JKw3ivq1r0V7ORvaqvVql6vj8fjwWBwOBxut9vj8fj6ZAnAbrfb7/fn8/l4PJ5OJ8/pdHq5XKT4k5pDMXi5XIKJmM/nOLfbLSE8tVptvV5vUxNTKCQJntls1u/3qRgOh4vFQnSpVCJnMplsNhv4+/1eAEZIYXYPBoBms4nZv6vUipmRIEciPdnq9xStkMjyruBvcHRFNCqaR6MRcCaSU7q34NxdIlVo2PV61QiJkBcW/ARHSTC9Xm+RGi1mDimFpyz71AgRlmeGV7/2IjdtEWDawenKeU2NX94n2B/izKlcLrdarXa7jQrACKPVr7Jx+PcJthX0hM5ofjYY12j7K9gI8zV3Op1ut4s2eG6pSSdvNi0HioRFFT/gkKdgWXWLx9idKSeSJzKKceWUUWuTbKVkEqSEUKWrZmbDkGTr6RBOKKJ+wDJpGG+8ZDzk4XHepBYC4xpPwd8bZoaxxuvUYqpZL/7f6syTKECr8BBmGYnU7Uaj8W6r8h8DQw4ly9Riw5T3GQymb7F3sY/w3hCez2AM0XqHGFjw/4pZb4zXU6l6hh/MQRc+g/0wxw7JEtvK8/ULS7wl1WqVbFSVSgW/afHkVvptt30oNdw3xMFT/4xK2a8jLbR/yZ+jkbWSJKAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 19 39 21&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-a67b7.png 200w,
/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-0b187.png 400w,
/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-fee1c.png 800w,
/static/2023-09-24-19-39-21-da9f406aa647b2b81d2a92672c7109b4-c7bf1.png 852w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;但是这样引发的后续问题就很多，比如说要是某个订单对应的请求特别多，造成某台机器成热点怎么办？解决这些问题又要开启后续一连串的复杂技术方案，曾经这类问题弄的我们头疼不已，所以，还是建议什么呢？&lt;/p&gt;
&lt;p&gt;最好是比如说刚才那种，一个订单的插入和删除操作，能不能合并成一个操作，就是一个删除，或者是其它什么，避免这种问题的产生。&lt;/p&gt;
&lt;h3 id=&quot;如何自己设计一个类似-dubbo-的-rpc-框架？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%87%AA%E5%B7%B1%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E7%B1%BB%E4%BC%BC-dubbo-%E7%9A%84-rpc-%E6%A1%86%E6%9E%B6%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何自己设计一个类似 Dubbo 的 RPC 框架？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;上来你的服务就得去注册中心注册吧，你是不是得有个注册中心，保留各个服务的信息，可以用 zookeeper 来做，对吧。&lt;/li&gt;
&lt;li&gt;然后你的消费者需要去注册中心拿对应的服务信息吧，对吧，而且每个服务可能会存在于多台机器上。&lt;/li&gt;
&lt;li&gt;接着你就该发起一次请求了，咋发起？当然是基于动态代理了，你面向接口获取到一个动态代理，这个动态代理就是接口在本地的一个代理，然后这个代理会找到服务对应的机器地址。&lt;/li&gt;
&lt;li&gt;然后找哪个机器发送请求？那肯定得有个负载均衡算法了，比如最简单的可以随机轮询是不是。&lt;/li&gt;
&lt;li&gt;接着找到一台机器，就可以跟它发送请求了，第一个问题咋发送？你可以说用 netty 了，nio 方式；第二个问题发送啥格式数据？你可以说用 hessian 序列化协议了，或者是别的，对吧。然后请求过去了。&lt;/li&gt;
&lt;li&gt;服务器那边一样的，需要针对你自己的服务生成一个动态代理，监听某个网络端口了，然后代理你本地的服务代码。接收到请求的时候，就调用对应的服务代码，对吧。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cap-定理的-p-是什么？&quot;&gt;&lt;a href=&quot;#cap-%E5%AE%9A%E7%90%86%E7%9A%84-p-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;CAP 定理的 P 是什么？&lt;/h3&gt;
&lt;h4 id=&quot;什么是-cap-定理（cap-theorem）&quot;&gt;&lt;a href=&quot;#%E4%BB%80%E4%B9%88%E6%98%AF-cap-%E5%AE%9A%E7%90%86%EF%BC%88cap-theorem%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;什么是 CAP 定理（CAP theorem）&lt;/h4&gt;
&lt;p&gt;在理论计算机科学中，CAP 定理（CAP theorem），又被称作布鲁尔定理（Brewer’s theorem），它指出对于一个分布式计算系统来说，不可能同时满足以下三点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一致性（Consistency）（等同于所有节点访问同一份最新的数据副本）&lt;/li&gt;
&lt;li&gt;可用性（Availability）（每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据）&lt;/li&gt;
&lt;li&gt;分区容错性（Partition tolerance）（以实际效果而言，分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性，就意味着发生了分区的情况，必须就当前操作在 C 和 A 之间做出选择。）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;分区容错性（partition-tolerance）&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%8C%BA%E5%AE%B9%E9%94%99%E6%80%A7%EF%BC%88partition-tolerance%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分区容错性（Partition tolerance）&lt;/h4&gt;
&lt;p&gt;理解 CAP 理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致，即丧失了 C 性质。如果为了保证数据一致性，将分区一侧的节点设置为不可用，那么又丧失了 A 性质。除非两个节点可以互相通信，才能既保证 C 又保证 A，这又会导致丧失 P 性质。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;P 指的是分区容错性，分区现象产生后需要容错，容错是指在 A 与 C 之间选择。如果分布式系统没有分区现象（没有出现不一致不可用情况）本身就没有分区，既然没有分区则就更没有分区容错性 P。&lt;/li&gt;
&lt;li&gt;无论我设计的系统是 AP 还是 CP 系统如果没有出现不一致不可用。则该系统就处于 CA 状态&lt;/li&gt;
&lt;li&gt;P 的体现前提是得有分区情况存在&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;常用的-cap-框架对比&quot;&gt;&lt;a href=&quot;#%E5%B8%B8%E7%94%A8%E7%9A%84-cap-%E6%A1%86%E6%9E%B6%E5%AF%B9%E6%AF%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;常用的 CAP 框架对比&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;框架&lt;/th&gt;
&lt;th&gt;所属&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Eureka&lt;/td&gt;
&lt;td&gt;AP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zookeeper&lt;/td&gt;
&lt;td&gt;CP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consul&lt;/td&gt;
&lt;td&gt;CP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5 id=&quot;eureka&quot;&gt;&lt;a href=&quot;#eureka&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Eureka&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;Eureka 保证了可用性，实现最终一致性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eureka 所有节点都是平等的所有数据都是相同的，且 Eureka 可以相互交叉注册。&lt;/p&gt;
&lt;p&gt;Eureka client 使用内置轮询负载均衡器去注册，有一个检测间隔时间，如果在一定时间内没有收到心跳，才会移除该节点注册信息；如果客户端发现当前 Eureka 不可用，会切换到其他的节点，如果所有的 Eureka 都跪了，Eureka client 会使用最后一次数据作为本地缓存；所以以上的每种设计都是他不具备一致性的特性。&lt;/p&gt;
&lt;p&gt;注意：因为 Eureka AP 的特性和请求间隔同步机制，在服务更新时候一般会手动通过 Eureka 的 api 把当前服务状态设置为 offline，并等待 2 个同步间隔后重新启动，这样就能保证服务更新节点对整体系统的影响&lt;/p&gt;
&lt;h5 id=&quot;zookeeper&quot;&gt;&lt;a href=&quot;#zookeeper&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Zookeeper&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;强一致性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Zookeeper 在选举 leader 时会停止服务，只有选举 leader 成功后才能提供服务，选举时间较长；内部使用 paxos 选举投票机制，只有获取半数以上的投票才能成为 leader，否则重新投票，所以部署的时候最好集群节点不小于 3 的奇数个（但是谁能保证跪掉后节点也是奇数个呢）；Zookeeper 健康检查一般是使用 tcp 长链接，在内部网络抖动时或者对应节点阻塞时候都会变成不可用，这里还是比较危险的；&lt;/p&gt;
&lt;h5 id=&quot;consul&quot;&gt;&lt;a href=&quot;#consul&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Consul&lt;/h5&gt;
&lt;p&gt;和 Zookeeper 一样数据 CP&lt;/p&gt;
&lt;p&gt;Consul 注册时候只有过半的节点都写入成功才认为注册成功；leader 挂掉时，重新选举期间整个 Consul 不可用，保证了强一致性但牺牲了可用性&lt;/p&gt;
&lt;p&gt;有很多 blog 说 Consul 属于 ap，官方已经确认他为 CP 机制，原文地址：&lt;a href=&quot;https://www.consul.io/docs/intro/vs/serf&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://www.consul.io/docs/intro/vs/serf&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;分布式锁&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式锁&lt;/h2&gt;
&lt;h3 id=&quot;zookeeper-都有哪些应用场景？&quot;&gt;&lt;a href=&quot;#zookeeper-%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Zookeeper 都有哪些应用场景？&lt;/h3&gt;
&lt;p&gt;大致来说，zookeeper 的使用场景如下，我就举几个简单的，大家能说几个就好了：&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;HA 高可用性&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;分布式协调&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E5%8D%8F%E8%B0%83&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式协调&lt;/h4&gt;
&lt;p&gt;这个其实是 zookeeper 很经典的一个用法，简单来说，就好比，你 A 系统发送个请求到 mq，然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果？用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器，一旦 B 系统处理完了就修改 zookeeper 那个节点的值，A 系统立马就可以收到通知，完美解决。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-25115.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 41.45907473309609%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAA90lEQVQY01WQxw6DMAyG8/6vhxCIQzkQ9t6b9GssVa0Pxnb8D6Oe57muyxhDQV7X1fM8x3H6vqc9z7NtW+ppmmjZ1FpHUVTXNa3iw1ue52VZJknyXSUYVlVFAUXXddQsN00j+b5vBYZt+nmesyxD9mUjCAK4ShvLsoAEH8cxXML+AePkOA5jY7YBESziE00mZGkBjOOILNd9bDOFkp6MCM+sgkdNFCjICLAAkuEwDCD3fVdM6fGDAs+Aceu6bhiGvu8XRcFEVrUNOSpNU9wp8xMQIyv/H0FaiGCUm/HFZNs29ITxD8wlPGOBAufswYJJfjt4YZRrJd559cax7K+29wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 19 47 25&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-a67b7.png 200w,
/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-0b187.png 400w,
/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-fee1c.png 800w,
/static/2023-09-24-19-47-25-9f0d7d28f23b0ab0e6eaf59e8f823073-25115.png 1124w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;分布式锁-1&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式锁&lt;/h4&gt;
&lt;p&gt;举个栗子。对某一个数据连续发出两个修改操作，两台机器同时收到了请求，但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁，一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁，就是可以去创建一个 znode，接着执行操作；然后另外一个机器也尝试去创建那个 znode，结果发现自己创建不了，因为被别人创建了，那只能等着，等第一个机器执行完了自己再执行。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-99df0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 45.489443378119%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABEElEQVQoz22RWW+DQAyE+f9/LU9IRCBxH+G+zxAK26+sRJsqfjC79sx4zCpCiKqqfN/3PC+KIl3XLcsax5H6MAyu69IKgoC64zjLsog/ochP13XzPJO3beNA5TgO8uv14tr3/bquaZqK91AAQciybJqmtm3JDLzIjIIp62Ck7ttkyFIb///aSFCv6xoyMADPM6RBBTN5nkMGCgj5siyBcmUgLXaBgwU47K9p2u12s20bpIIwDWmV/xSGYZIkRVGgbZrm/X5XVdUwDDigAQP7OuPXNvIIk6XW4/HAEZlifgaYa8a10Q9533dmgo7jGASem6bh2dDiyg/HNmcwSHx4Khm04TOQh4WAeR6f9VhEfIpv6zsBGfjYKSMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 19 47 54&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-a67b7.png 200w,
/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-0b187.png 400w,
/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-fee1c.png 800w,
/static/2023-09-24-19-47-54-62f8bec95e5312c6e8eba17c02e578e0-99df0.png 1042w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;元数据配置信息管理&quot;&gt;&lt;a href=&quot;#%E5%85%83%E6%95%B0%E6%8D%AE%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E7%AE%A1%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;元数据/配置信息管理&lt;/h4&gt;
&lt;p&gt;zookeeper 可以用作很多系统的配置信息的管理，比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理，包括 dubbo 注册中心不也支持 zookeeper 么？&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-9725a.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 61.29032258064516%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsSAAALEgHS3X78AAABJ0lEQVQoz43S227CQAwE0P3//0NI8EJCuCRBNBTKJSS0p7EUVUhU+GEVe2fGY2/S99vR933XdR9DHA6HsizTO7TH4+H8HGK326HleV7XdfoH/RTH41Hztm33+z0J/ZPkcrlA32636/XqbIcA/RridDppiBbn/X4/n8+Q6mm73TJQFMVsNptOp/P5fLFYcAVhsKqq4KTqGiIA0yUkTa7xNR/d2spyuUSG4I2X9Xq92WxouXJKsyxTSSGMTI8wnwhUV6uVom8qJqTbNE0/BFESv7ajYfcnYiuucUjwRTcWBqlZzCx9ue3wrBuyD6Km5ciYrjjHf/nO0M0Qsc7JZGK6mIsdS6WSnt7WHeE49QTyS8TyFE00In0/k40K6jGNHW2lvlXw41HG+AEfX7LTwJCs8AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 19 48 32&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-a67b7.png 200w,
/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-0b187.png 400w,
/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-fee1c.png 800w,
/static/2023-09-24-19-48-32-db985923e85024a0b3f3383bc97df5d6-9725a.png 1085w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;ha-高可用性&quot;&gt;&lt;a href=&quot;#ha-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;HA 高可用性&lt;/h4&gt;
&lt;p&gt;这个应该是很常见的，比如 hadoop、hdfs、yarn 等很多大数据系统，都选择基于 zookeeper 来开发 HA 高可用机制，就是一个重要进程一般会做主备两个，主进程挂了立马通过 zookeeper 感知到并切换到备用进程。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-62377.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 46.61016949152542%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABBElEQVQoz12RB46FQAxDuf8BAVGFEL0tvTP/fbJC7Foigokdm4wWx3Hf923bdl2Xpul5nkqp67qo8zzneS5dalmWy7JIV6BJDxzHMQyDiAVQ0f/coAvTcRzP8yzLsm07CAKtqipI0zRR67p+O3PCUPTI1nWl2zQNBmRkHC2Nj3EcYWRZBkm9wCBpYVIUBYJnrkDjIRI8BtPGQWRC2rYNJYbLDfUX33+OosgwDF3X4bGV93he0EtU3/dd14VDRuJ8xZjgLPvk/5Mk4Z0UfKKhYssJS6GGYSi387ttBuz7LvvEhNjxDZQSFR8O5xv/Y6PBhwswTZNUXINsGCsOJQIm1OeSH/EHGfEDR0pXgl4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 19 49 05&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-a67b7.png 200w,
/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-0b187.png 400w,
/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-fee1c.png 800w,
/static/2023-09-24-19-49-05-a6ca10a7ae52a5066f638f0999df8e4d-62377.png 1062w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&quot;分布式锁如何设计？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式锁如何设计？&lt;/h3&gt;
&lt;p&gt;其实一般问问题，都是这么问的，先问问你 zk，然后其实是要过渡到 zk 相关的一些问题里去，比如分布式锁。因为在分布式系统开发中，分布式锁的使用场景还是很常见的。&lt;/p&gt;
&lt;h4 id=&quot;redis-分布式锁&quot;&gt;&lt;a href=&quot;#redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 分布式锁&lt;/h4&gt;
&lt;p&gt;官方叫做 RedLock 算法，是 Redis 官方支持的分布式锁算法。&lt;/p&gt;
&lt;p&gt;这个分布式锁有 3 个重要的考量点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互斥（只能有一个客户端获取锁）&lt;/li&gt;
&lt;li&gt;不能死锁&lt;/li&gt;
&lt;li&gt;容错（只要大部分 Redis 节点创建了这把锁就可以）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;redis-最普通的分布式锁&quot;&gt;&lt;a href=&quot;#redis-%E6%9C%80%E6%99%AE%E9%80%9A%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis 最普通的分布式锁&lt;/h5&gt;
&lt;p&gt;第一个最普通的实现方式，就是在 Redis 里使用 &lt;code class=&quot;language-text&quot;&gt;SET key value [EX seconds] [PX milliseconds] NX&lt;/code&gt; 创建一个 key，这样就算加锁。其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NX：表示只有 key 不存在的时候才会设置成功，如果此时 redis 中存在这个 key，那么设置失败，返回 nil。&lt;/li&gt;
&lt;li&gt;EX seconds：设置 key 的过期时间，精确到秒级。意思是 seconds 秒后锁自动释放，别人创建的时候如果发现已经有了就不能加锁了。&lt;/li&gt;
&lt;li&gt;PX milliseconds：同样是设置 key 的过期时间，精确到毫秒级。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如执行以下命令：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;70557713754919750000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`SET resource_name my_random_value PX 30000 NX`, `70557713754919750000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;bash&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;bash&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-bash line-numbers&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;SET resource_name my_random_value PX &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt; NX&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;释放锁就是删除 key，但是一般可以用 lua 脚本删除，判断 value 一样才删除：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;47592441058961146000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`-- 删除锁的时候，找到 key 对应的 value，跟自己传过去的 value 做比较，如果是一样的才删除。
if redis.call(&amp;quot;get&amp;quot;, KEYS[1]) == ARGV[1] then
    return redis.call(&amp;quot;del&amp;quot;, KEYS[1])
else
    return 0
end`, `47592441058961146000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;lua&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;lua&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;lua&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-lua line-numbers&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- 删除锁的时候，找到 key 对应的 value，跟自己传过去的 value 做比较，如果是一样的才删除。&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; KEYS&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; ARGV&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;del&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; KEYS&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;为啥要用 &lt;code class=&quot;language-text&quot;&gt;random_value&lt;/code&gt; 随机值呢？因为如果某个客户端获取到了锁，但是阻塞了很长时间才执行完，比如说超过了 30s，此时可能已经自动释放锁了，此时可能别的客户端已经获取到了这个锁，要是你这个时候直接删除 key 的话会有问题，所以得用随机值加上面的 lua 脚本来释放锁。&lt;/p&gt;
&lt;p&gt;但是这样是肯定不行的。因为如果是普通的 Redis 单实例，那就是单点故障。或者是 Redis 普通主从，那 Redis 主从异步复制，如果主节点挂了（key 就没有了），key 还没同步到从节点，此时从节点切换为主节点，别人就可以 set key，从而拿到锁。&lt;/p&gt;
&lt;h5 id=&quot;redlock-算法&quot;&gt;&lt;a href=&quot;#redlock-%E7%AE%97%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RedLock 算法&lt;/h5&gt;
&lt;p&gt;这个场景是假设有一个 Redis cluster，有 5 个 Redis master 实例。然后执行如下步骤获取一把锁：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取当前时间戳，单位是毫秒；&lt;/li&gt;
&lt;li&gt;跟上面类似，轮流尝试在每个 master 节点上创建锁，超时时间较短，一般就几十毫秒（客户端为了获取锁而使用的超时时间比自动释放锁的总时间要小。例如，如果自动释放时间是 10 秒，那么超时时间可能在 5~50 毫秒范围内）；&lt;/li&gt;
&lt;li&gt;尝试在大多数节点上建立一个锁，比如 5 个节点就要求是 3 个节点 n / 2 + 1 ；&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;div class=&quot;mermaid&quot;&gt;graph TB
   A[&quot;系统 A&quot;]
   B[&quot;系统 A&quot;]
   C[&quot;系统 A&quot;]
   B --&gt; D((&quot;redis master&quot;))
   B ~~~ E((&quot;redis master&quot;))
   B --&gt; F((&quot;redis master&quot;))
   B ~~~ G((&quot;redis master&quot;))
   B --&gt; H((&quot;redis master&quot;))&lt;/div&gt;
&lt;p&gt;Redis 官方给出了以上两种基于 Redis 实现分布式锁的方法，详细说明可以查看：&lt;a href=&quot;https://redis.io/topics/distlock%E3%80%82&quot; target=&quot;_blank&quot; rel=&quot;nofollow noreferrer noopener&quot;&gt;https://redis.io/topics/distlock。&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;zk-分布式锁&quot;&gt;&lt;a href=&quot;#zk-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;zk 分布式锁&lt;/h4&gt;
&lt;p&gt;zk 分布式锁，其实可以做的比较简单，就是某个节点尝试创建临时 znode，此时创建成功了就获取了这个锁；这个时候别的客户端来创建锁会失败，只能注册个监听器监听这个锁。释放锁就是删除这个 znode，一旦释放掉就会通知客户端，然后有一个等待着的客户端就可以再次重新加锁。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;34022013168996980000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`/**
 * ZooKeeperSession
 */
public class ZooKeeperSession {

    private static CountDownLatch connectedSemaphore = new CountDownLatch(1);

    private ZooKeeper zookeeper;
    private CountDownLatch latch;

    public ZooKeeperSession() {
        try {
            this.zookeeper = new ZooKeeper(&amp;quot;192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181&amp;quot;, 50000, new ZooKeeperWatcher());
            try {
                connectedSemaphore.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(&amp;quot;ZooKeeper session established......&amp;quot;);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取分布式锁
     *
     * @param productId
     */
    public Boolean acquireDistributedLock(Long productId) {
        String path = &amp;quot;/product-lock-&amp;quot; + productId;

        try {
            zookeeper.create(path, &amp;quot;&amp;quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            return true;
        } catch (Exception e) {
            while (true) {
                try {
                    // 相当于是给 node 注册一个监听器，去看看这个监听器是否存在
                    Stat stat = zk.exists(path, true);

                    if (stat != null) {
                        this.latch = new CountDownLatch(1);
                        this.latch.await(waitTime, TimeUnit.MILLISECONDS);
                        this.latch = null;
                    }
                    zookeeper.create(path, &amp;quot;&amp;quot;.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                    return true;
                } catch (Exception ee) {
                    continue;
                }
            }

        }
        return true;
    }

    /**
     * 释放掉一个分布式锁
     *
     * @param productId
     */
    public void releaseDistributedLock(Long productId) {
        String path = &amp;quot;/product-lock-&amp;quot; + productId;
        try {
            zookeeper.delete(path, -1);
            System.out.println(&amp;quot;release the lock for product[id=&amp;quot; + productId + &amp;quot;]......&amp;quot;);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 建立 zk session 的 watcher
     */
    private class ZooKeeperWatcher implements Watcher {

        public void process(WatchedEvent event) {
            System.out.println(&amp;quot;Receive watched event: &amp;quot; + event.getState());

            if (KeeperState.SyncConnected == event.getState()) {
                connectedSemaphore.countDown();
            }

            if (this.latch != null) {
                this.latch.countDown();
            }
        }

    }

    /**
     * 封装单例的静态内部类
     */
    private static class Singleton {

        private static ZooKeeperSession instance;

        static {
            instance = new ZooKeeperSession();
        }

        public static ZooKeeperSession getInstance() {
            return instance;
        }

    }

    /**
     * 获取单例
     *
     * @return
     */
    public static ZooKeeperSession getInstance() {
        return Singleton.getInstance();
    }

    /**
     * 初始化单例的便捷方法
     */
    public static void init() {
        getInstance();
    }

}`, `34022013168996980000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * ZooKeeperSession
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; connectedSemaphore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeper&lt;/span&gt; zookeeper&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;zookeeper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperWatcher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                connectedSemaphore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ZooKeeper session established......&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 获取分布式锁
     *
     * @param productId
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;acquireDistributedLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/product-lock-&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            zookeeper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ids&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OPEN_ACL_UNSAFE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EPHEMERAL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 相当于是给 node 注册一个监听器，去看看这个监听器是否存在&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;Stat&lt;/span&gt; stat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stat &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;waitTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    zookeeper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ids&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OPEN_ACL_UNSAFE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EPHEMERAL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ee&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 释放掉一个分布式锁
     *
     * @param productId
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;releaseDistributedLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/product-lock-&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            zookeeper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;release the lock for product[id=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;]......&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 建立 zk session 的 watcher
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperWatcher&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Watcher&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WatchedEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Receive watched event: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeeperState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SyncConnected&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                connectedSemaphore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 封装单例的静态内部类
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Singleton&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 获取单例
     *
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperSession&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Singleton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 初始化单例的便捷方法
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;也可以采用另一种方式，创建临时顺序节点：&lt;/p&gt;
&lt;p&gt;如果有一把锁，被多个人给竞争，此时多个人会排队，第一个拿到锁的人会执行，然后释放锁；后面的每个人都会去监听排在自己前面的那个人创建的 node 上，一旦某个人释放了锁，排在自己后面的人就会被 ZooKeeper 给通知，一旦被通知了之后，就 ok 了，自己就获取到了锁，就可以执行代码了。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;54332210915808756000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class ZooKeeperDistributedLock implements Watcher {

    private ZooKeeper zk;
    private String locksRoot = &amp;quot;/locks&amp;quot;;
    private String productId;
    private String waitNode;
    private String lockNode;
    private CountDownLatch latch;
    private CountDownLatch connectedLatch = new CountDownLatch(1);
    private int sessionTimeout = 30000;

    public ZooKeeperDistributedLock(String productId) {
        this.productId = productId;
        try {
            String address = &amp;quot;192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181&amp;quot;;
            zk = new ZooKeeper(address, sessionTimeout, this);
            connectedLatch.await();
        } catch (IOException e) {
            throw new LockException(e);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public void process(WatchedEvent event) {
        if (event.getState() == KeeperState.SyncConnected) {
            connectedLatch.countDown();
            return;
        }

        if (this.latch != null) {
            this.latch.countDown();
        }
    }

    public void acquireDistributedLock() {
        try {
            if (this.tryLock()) {
                return;
            } else {
                waitForLock(waitNode, sessionTimeout);
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    public boolean tryLock() {
        try {
            // 传入进去的 locksRoot + &amp;quot;/&amp;quot; + productId
            // 假设 productId 代表了一个商品 id，比如说 1
            // locksRoot = locks
            // /locks/10000000000，/locks/10000000001，/locks/10000000002
            lockNode = zk.create(locksRoot + &amp;quot;/&amp;quot; + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            // 看看刚创建的节点是不是最小的节点
            // locks：10000000000，10000000001，10000000002
            List&lt;String&gt; locks = zk.getChildren(locksRoot, false);
            Collections.sort(locks);

            if (lockNode.equals(locksRoot + &amp;quot;/&amp;quot; + locks.get(0))) {
                // 如果是最小的节点，则表示取得锁
                return true;
            }

            // 如果不是最小的节点，找到比自己小 1 的节点
            int previousLockIndex = -1;
            for (int i = 0; i &lt; locks.size(); i++) {
                if (lockNode.equals(locksRoot + &amp;quot;/&amp;quot; +locks.get(i))){
                    previousLockIndex = i - 1;
                    break;
                }
            }

            this.waitNode = locks.get(previousLockIndex);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
        return false;
    }

    private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
        Stat stat = zk.exists(locksRoot + &amp;quot;/&amp;quot; + waitNode, true);
        if (stat != null) {
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);
            this.latch = null;
        }
        return true;
    }

    public void unlock() {
        try {
            // 删除 /locks/10000000000 节点
            // 删除 /locks/10000000001 节点
            System.out.println(&amp;quot;unlock &amp;quot; + lockNode);
            zk.delete(lockNode, -1);
            lockNode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public LockException(String e) {
            super(e);
        }

        public LockException(Exception e) {
            super(e);
        }
    }
}`, `54332210915808756000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperDistributedLock&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Watcher&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeper&lt;/span&gt; zk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; locksRoot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/locks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; waitNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; lockNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; latch&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt; connectedLatch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; sessionTimeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeperDistributedLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; address &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            zk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooKeeper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessionTimeout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            connectedLatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeeperException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WatchedEvent&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KeeperState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SyncConnected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            connectedLatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;countDown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;acquireDistributedLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;waitForLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;waitNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessionTimeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeeperException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 传入进去的 locksRoot + &quot;/&quot; + productId&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 假设 productId 代表了一个商品 id，比如说 1&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// locksRoot = locks&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// /locks/10000000000，/locks/10000000001，/locks/10000000002&lt;/span&gt;
            lockNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locksRoot &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZooDefs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ids&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OPEN_ACL_UNSAFE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EPHEMERAL_SEQUENTIAL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 看看刚创建的节点是不是最小的节点&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// locks：10000000000，10000000001，10000000002&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; locks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChildren&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locksRoot&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locksRoot &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 如果是最小的节点，则表示取得锁&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 如果不是最小的节点，找到比自己小 1 的节点&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; previousLockIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locksRoot &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    previousLockIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;waitNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;previousLockIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeeperException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitForLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; waitNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; waitTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KeeperException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Stat&lt;/span&gt; stat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locksRoot &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; waitNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stat &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountDownLatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;waitTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 删除 /locks/10000000000 节点&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 删除 /locks/10000000001 节点&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unlock &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; lockNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockNode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            lockNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            zk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeeperException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; serialVersionUID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是，使用 zk 临时节点会存在另一个问题：由于 zk 依靠 session 定期的心跳来维持客户端，如果客户端进入长时间的 GC，可能会导致 zk 认为客户端宕机而释放锁，让其他的客户端获取锁，但是客户端在 GC 恢复后，会认为自己还持有锁，从而可能出现多个客户端同时获取到锁的情形。&lt;/p&gt;
&lt;p&gt;针对这种情况，可以通过 JVM 调优，尽量避免长时间 GC 的情况发生。&lt;/p&gt;
&lt;h4 id=&quot;redis-分布式锁和-zk-分布式锁的对比&quot;&gt;&lt;a href=&quot;#redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C-zk-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AF%B9%E6%AF%94&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;redis 分布式锁和 zk 分布式锁的对比&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;redis 分布式锁，其实需要自己不断去尝试获取锁，比较消耗性能。&lt;/li&gt;
&lt;li&gt;zk 分布式锁，获取不到锁，注册个监听器即可，不需要不断主动尝试获取锁，性能开销较小。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外一点就是，如果是 Redis 获取锁的那个客户端 出现 bug 挂了，那么只能等待超时时间之后才能释放锁；而 zk 的话，因为创建的是临时 znode，只要客户端挂了，znode 就没了，此时就自动释放锁。&lt;/p&gt;
&lt;p&gt;Redis 分布式锁大家没发现好麻烦吗？遍历上锁，计算时间等等，zk 的分布式锁语义清晰实现简单。&lt;/p&gt;
&lt;p&gt;所以先不分析太多的东西，就说这两点，我个人实践认为 zk 的分布式锁比 Redis 的分布式锁牢靠、而且模型简单易用。&lt;/p&gt;
&lt;h2 id=&quot;分布式事务&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式事务&lt;/h2&gt;
&lt;h3 id=&quot;分布式事务了解吗？&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E4%BA%86%E8%A7%A3%E5%90%97%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式事务了解吗？&lt;/h3&gt;
&lt;p&gt;只要聊到你做了分布式系统，必问分布式事务，你对分布式事务一无所知的话，确实会很坑，你起码得知道有哪些方案，一般怎么来做，每个方案的优缺点是什么。&lt;/p&gt;
&lt;p&gt;现在面试，分布式系统成了标配，而分布式系统带来的分布式事务也成了标配了。因为你做系统肯定要用事务吧，如果是分布式系统，肯定要用分布式事务吧。先不说你搞过没有，起码你得明白有哪几种方案，每种方案可能有啥坑？比如 TCC 方案的网络问题、XA 方案的一致性问题。&lt;/p&gt;
&lt;p&gt;分布式事务的实现主要有以下 6 种方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XA 方案&lt;/li&gt;
&lt;li&gt;TCC 方案&lt;/li&gt;
&lt;li&gt;SAGA 方案&lt;/li&gt;
&lt;li&gt;本地消息表&lt;/li&gt;
&lt;li&gt;可靠消息最终一致性方案&lt;/li&gt;
&lt;li&gt;最大努力通知方案&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;两阶段提交方案xa-方案&quot;&gt;&lt;a href=&quot;#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E6%96%B9%E6%A1%88xa-%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;两阶段提交方案/XA 方案&lt;/h4&gt;
&lt;p&gt;所谓的 XA 方案，即：两阶段提交，有一个事务管理器的概念，负责协调多个数据库（资源管理器）的事务，事务管理器先问问各个数据库你准备好了吗？如果每个数据库都回复 ok，那么就正式提交事务，在各个数据库上执行操作；如果任何其中一个数据库回答不 ok，那么就回滚事务。&lt;/p&gt;
&lt;p&gt;这种分布式事务方案，比较适合单块应用里，跨多个库的分布式事务，而且因为严重依赖于数据库层面来搞定复杂的事务，效率很低，绝对不适合高并发的场景。如果要玩儿，那么基于 Spring + JTA 就可以搞定，自己随便搜个 demo 看看就知道了。&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;div class=&quot;mermaid&quot;&gt;graph LR
   subgraph 系统
      事务管理器
   end
   事务管理器 --&gt; 数据库1
   事务管理器 --&gt; 数据库2
   事务管理器 --&gt; 数据库3&lt;/div&gt;
&lt;h4 id=&quot;tcc-方案&quot;&gt;&lt;a href=&quot;#tcc-%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TCC 方案&lt;/h4&gt;
&lt;p&gt;TCC 的全称是：Try、Confirm、Cancel。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try 阶段：这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。&lt;/li&gt;
&lt;li&gt;Confirm 阶段：这个阶段说的是在各个服务中执行实际的操作。&lt;/li&gt;
&lt;li&gt;Cancel 阶段：如果任何一个服务的业务方法执行出错，那么这里就需要进行补偿，就是执行已经执行成功的业务逻辑的回滚操作。（把那些执行成功的回滚）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种方案说实话几乎很少人使用，我们用的也比较少，但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了，会造成补偿代码巨大，非常之恶心。&lt;/p&gt;
&lt;p&gt;比如说我们，一般来说跟钱相关的，跟钱打交道的，支付、交易相关的场景，我们会用 TCC，严格保证分布式事务要么全部成功，要么全部自动回滚，严格保证资金的正确性，保证在资金上不会出现问题。&lt;/p&gt;
&lt;p&gt;而且最好是你的各个业务执行的时间都比较短。&lt;/p&gt;
&lt;p&gt;但是说实话，一般尽量别这么搞，自己手写回滚逻辑，或者是补偿逻辑，实在太恶心了，那个业务代码是很难维护的。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28437054921921077000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Try 阶段：冻结银行资金

Confirm 阶段

1. 在自己的库里插入数据
2. 调用银行 B 的接囗，扣款
3. 调用银行 C 的接口，转账

Cancel 阶段：回滚

将银行 B 的扣款给他加回去`, `28437054921921077000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Try 阶段：冻结银行资金

Confirm 阶段

1. 在自己的库里插入数据
2. 调用银行 B 的接囗，扣款
3. 调用银行 C 的接口，转账

Cancel 阶段：回滚

将银行 B 的扣款给他加回去&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;mermaid&quot;&gt;graph TB
   用户 --&gt; 系统A
   系统A --&gt; 数据库
   系统A --&gt; 银行B --&gt; A[数据库]
   系统A --&gt; 银行C --&gt; B[数据库]&lt;/div&gt;
&lt;h4 id=&quot;saga-方案&quot;&gt;&lt;a href=&quot;#saga-%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Saga 方案&lt;/h4&gt;
&lt;p&gt;金融核心等业务可能会选择 TCC 方案，以追求强一致性和更高的并发量，而对于更多的金融核心以上的业务系统 往往会选择补偿事务，补偿事务处理在 30 多年前就提出了 Saga 理论，随着微服务的发展，近些年才逐步受到大家的关注。目前业界比较公认的是采用 Saga 作为长事务的解决方案。&lt;/p&gt;
&lt;h5 id=&quot;基本原理&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基本原理&lt;/h5&gt;
&lt;p&gt;业务流程中每个参与者都提交本地事务，若某一个参与者失败，则补偿前面已经成功的参与者。下图左侧是正常的事务流程，当执行到 T3 时发生了错误，则开始执行右边的事务补偿流程，反向执行 T3、T2、T1 的补偿服务 C3、C2、C1，将 T3、T2、T1 已经修改的数据补偿掉。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-20-20-26-4f6e2fd242e91975178bd7d994b2b7c8-99ffa.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 793px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 102.26986128625472%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAADEElEQVQ4y3WU227bRhCG/ZJBEBi5SXKVPEmA9iZvkYvGgGsgaAuhRYDqLMeSKMsSD6JIKeJZFLnnXZIZUrIdN+iAWAx399/Z+WbIM8aYEIKz2iiljDLB67eqqmCe1lOMUIowhpE0OwghRWNnINkeXDux3MPaSW1wIhQKLmqxUuJw4LrODUNZFjhsuVQIwXzV2BlhZOZNbgPt73FrYPU0f2yGOkaEc44oQ67L+/2829UvL/loRNptHgSiKKqyrMWUkbtAA/3IHo43X6fejZPYELksS6EK6vu030eDgfflC7u+xt2uiONHMUQgkmCRb5M0Qhk4XPHTtYWQnBdZLuOIapqIIpVlhZRciNO1QXz0tpFMUQEOHHoEBkuqSQ8EyNDLQjWr5YPkjHG2y7ZuYhsh0Fo7ySrBsRTyBCzLmGkww+DTKTjU0BXGQsqnwEKtdf1XZ/GvFkysyICaKCmpEHi7AWCo19MuPpFBn3Q6PAx/BEbnvnbrawOzN1oNNH+yjleccSgjA723o4MBGY7cVgsPhwSARdETYIjnGdu7SRLmaUb3VNJjVjUwxlSa8t2OTiYiCOR+X0BHPeQs7tFtQpEidQLWTEJuqqgRgiBbLMsm2s/Avjn7tRk5duI46TrJQ7rzhOchy+T7hFlWDUzTwKHQahhxIf/bYf+MWz29rQVT01tk22/Icfb6Epkm73RQr7+4+p1A8j8BI3N/OvMnXb09tHpTf+wcXCkULMPVaZpCe6B+3/78GYO46TCo/iMwLDBiByfeByjL8hB5G8ipuLlhV1f5xUX28SPZbgEVCQJ4GMHy4cPgdSfCOcoJcZw1jdXtsMtL9f59dX5evXpVvHtXIHRkdQRW3VtdZzNeLsLbuT9bBPO7YLajgfztk3z9enV+rj97lr19WyFU771P9VF832GzP4d/dJZtzZ/akQE01a+/BM+fuy9e4AfxU+UpshUby3AOYZfB3SK49Q4byER++FC9fFm9eQPXLvP8f8QEfis4xzkiqB7hP0Awg1LaK3bzlU3GTNNKpcqjGvTl6RAYvwNRcU9jXQNFIwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 20 20 26&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-20-20-26-4f6e2fd242e91975178bd7d994b2b7c8-99ffa.png&quot; data-srcset=&quot;/static/2023-09-24-20-20-26-4f6e2fd242e91975178bd7d994b2b7c8-e6b42.png 200w,
/static/2023-09-24-20-20-26-4f6e2fd242e91975178bd7d994b2b7c8-2a82f.png 400w,
/static/2023-09-24-20-20-26-4f6e2fd242e91975178bd7d994b2b7c8-99ffa.png 793w&quot; data-sizes=&quot;(max-width: 793px) 100vw, 793px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h5 id=&quot;使用场景&quot;&gt;&lt;a href=&quot;#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;使用场景&lt;/h5&gt;
&lt;p&gt;对于一致性要求高、短流程、并发高的场景，如：金融核心系统，会优先考虑 TCC 方案。而在另外一些场景下，我们并不需要这么强的一致性，只需要保证最终一致性即可。&lt;/p&gt;
&lt;p&gt;比如很多金融核心以上的业务（渠道层、产品层、系统集成层），这些系统的特点是最终一致即可、流程多、流程长、还可能要调用其它公司的服务。这种情况如果选择 TCC 方案开发的话，一来成本高，二来无法要求其它公司的服务也遵循 TCC 模式。同时流程长，事务边界太长，加锁时间长，也会影响并发性能。&lt;/p&gt;
&lt;p&gt;所以 Saga 模式的适用场景是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务流程长、业务流程多；&lt;/li&gt;
&lt;li&gt;参与者包含其它公司或遗留系统服务，无法提供 TCC 模式要求的三个接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;优点-1&quot;&gt;&lt;a href=&quot;#%E4%BC%98%E7%82%B9-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;优点&lt;/h5&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;h5 id=&quot;缺点-1&quot;&gt;&lt;a href=&quot;#%E7%BC%BA%E7%82%B9-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;缺点&lt;/h5&gt;
&lt;p&gt;不保证事务的隔离性。&lt;/p&gt;
&lt;h4 id=&quot;本地消息表&quot;&gt;&lt;a href=&quot;#%E6%9C%AC%E5%9C%B0%E6%B6%88%E6%81%AF%E8%A1%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;本地消息表&lt;/h4&gt;
&lt;p&gt;本地消息表其实是国外的 ebay 搞出来的这么一套思想。&lt;/p&gt;
&lt;p&gt;这个大概意思是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A 系统在自己本地一个事务里操作同时，插入一条数据到消息表；&lt;/li&gt;
&lt;li&gt;接着 A 系统将这个消息发送到 MQ 中去；&lt;/li&gt;
&lt;li&gt;B 系统接收到消息之后，在一个事务里，往自己本地消息表里插入一条数据，同时执行其他的业务操作，如果这个消息已经被处理过了，那么此时这个事务会回滚，这样保证不会重复处理消息；&lt;/li&gt;
&lt;li&gt;B 系统执行成功之后，就会更新自己本地消息表的状态以及 A 系统消息表的状态；&lt;/li&gt;
&lt;li&gt;如果 B 系统处理失败了，那么就不会更新消息表状态，那么此时 A 系统会定时扫描自己的消息表，如果有未处理的消息，会再次发送到 MQ 中去，让 B 再次处理；&lt;/li&gt;
&lt;li&gt;这个方案保证了最终一致性，哪怕 B 事务失败了，但是 A 会不断重发消息，直到 B 那边成功为止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个方案说实话最大的问题就在于严重依赖于数据库的消息表来管理事务啥的，如果是高并发场景咋办呢？咋扩展呢？所以一般确实很少用。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-cf7a7.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 42.51012145748987%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA+klEQVQoz4WRxw6DMAyG8/5PhzhwACHELHuPEgTpVyIhtRzwKbb/YTsiSZIsy9Z1jePYtu2qqsqyfJ0xz7NSqmka0zRpHcehfkP4vh+GIbi2bfM8L4qi67plWaSU0zS9zxjHse97ingA402XVKAHGsM0TTGBrNvIMUJd16RASYGRMiBIAMMwiG3b4JDQQxgfqngiSlELUQcDByYpTlEUIfclQ9BzMh4rIK9X2vcdxNXSUxBMxILICV5akrMhCQiCPOPvPNfBMOPMUITmoBQEAZMzs+d5lmXRVk8hsIXDJqjgiTMP13WpPJM5BhMiYRiG4zi8WZVD3H/1Hh9MEwIVwJg8IAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 20 22 38&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-a67b7.png 200w,
/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-0b187.png 400w,
/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-fee1c.png 800w,
/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-b1a91.png 1200w,
/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-95179.png 1600w,
/static/2023-09-24-20-22-38-bb409d68e17c59bab402e21c73dc5d57-cf7a7.png 1729w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;可靠消息最终一致性方案&quot;&gt;&lt;a href=&quot;#%E5%8F%AF%E9%9D%A0%E6%B6%88%E6%81%AF%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A7%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;可靠消息最终一致性方案&lt;/h4&gt;
&lt;p&gt;这个的意思，就是干脆不要用本地的消息表了，直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。&lt;/p&gt;
&lt;p&gt;大概的意思就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A 系统先发送一个 prepared 消息到 mq，如果这个 prepared 消息发送失败那么就直接取消操作别执行了；&lt;/li&gt;
&lt;li&gt;如果这个消息发送成功过了，那么接着执行本地事务，如果成功就告诉 mq 发送确认消息，如果失败就告诉 mq 回滚消息；&lt;/li&gt;
&lt;li&gt;如果发送了确认消息，那么此时 B 系统会接收到确认消息，然后执行本地的事务；&lt;/li&gt;
&lt;li&gt;mq 会自动定时轮询所有 prepared 消息回调你的接口，问你，这个消息是不是本地事务处理失败了，所有没发送确认的消息，是继续重试还是回滚？一般来说这里你就可以查下数据库看之前本地事务是否执行，如果回滚了，那么这里也回滚吧。这个就是避免可能本地事务执行成功了，而确认消息却发送失败了。&lt;/li&gt;
&lt;li&gt;这个方案里，要是系统 B 的事务失败了咋办？重试咯，自动不断重试直到成功，如果实在是不行，要么就是针对重要的资金类业务进行回滚，比如 B 系统本地回滚后，想办法通知系统 A 也回滚；或者是发送报警由人工来手工回滚和补偿。&lt;/li&gt;
&lt;li&gt;这个还是比较合适的，目前国内互联网公司大都是这么玩儿的，要不你就用 RocketMQ 支持的，要不你就自己基于类似 ActiveMQ？RabbitMQ？自己封装一套类似的逻辑出来，总之思路就是这样子的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-9c8dd.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 45.80838323353294%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABC0lEQVQoz12R6YqDQBCE8/7vJv4QBC9cFS+8Nep6br5MB7NJgUNPd1dV93jL8zyKojRN27a9K0zTRPyjkGXZPM+/CpPC3z/c4jg2TdP3/bIs13Wlqes64qqqlmWByUl+27bzPIVM8CLzUaADk02BeN93VCiN48hcmqY5jjMMQ9/3lI7jWBSeZORxY1ROupGniSsBGaokJcNSBHVdF0XB9UlGjBR3TgZumgaaOBNQleloSJIEDlugguiLXCuwv+d5YRg2CuIm60EmA5/hP3ZGQ2aOFCAj5LquZVlBEEgfbujato3BBxlneYArS4AJfNwuZ34qj8pf4MHe5C+IBN1MJAsDpA3D0HWdca7kAw1tAhJhJ6vbAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 24 20 23 59&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-fee1c.png&quot; data-srcset=&quot;/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-a67b7.png 200w,
/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-0b187.png 400w,
/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-fee1c.png 800w,
/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-b1a91.png 1200w,
/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-95179.png 1600w,
/static/2023-09-24-20-23-59-c1847381caa4b800d98f2ab899a0679f-9c8dd.png 1670w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;最大努力通知方案&quot;&gt;&lt;a href=&quot;#%E6%9C%80%E5%A4%A7%E5%8A%AA%E5%8A%9B%E9%80%9A%E7%9F%A5%E6%96%B9%E6%A1%88&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;最大努力通知方案&lt;/h4&gt;
&lt;p&gt;这个方案的大致意思就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系统 A 本地事务执行完之后，发送个消息到 MQ；&lt;/li&gt;
&lt;li&gt;这里会有个专门消费 MQ 的最大努力通知服务，这个服务会消费 MQ 然后写入数据库中记录下来，或者是放入个内存队列也可以，接着调用系统 B 的接口；&lt;/li&gt;
&lt;li&gt;要是系统 B 执行成功就 ok 了；要是系统 B 执行失败了，那么最大努力通知服务就定时尝试重新调用系统 B，反复 N 次，最后还是不行就放弃。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;你们公司是如何处理分布式事务的？&quot;&gt;&lt;a href=&quot;#%E4%BD%A0%E4%BB%AC%E5%85%AC%E5%8F%B8%E6%98%AF%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%9A%84%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;你们公司是如何处理分布式事务的？&lt;/h4&gt;
&lt;p&gt;如果你真的被问到，可以这么说，我们某某特别严格的场景，用的是 TCC 来保证强一致性；然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。&lt;/p&gt;
&lt;p&gt;你找一个严格资金要求绝对不能错的场景，你可以说你是用的 TCC 方案；如果是一般的分布式事务场景，订单插入之后要调用库存服务更新库存，库存数据没有资金那么的敏感，可以用可靠消息最终一致性方案。&lt;/p&gt;
&lt;p&gt;友情提示一下，RocketMQ 3.2.6 之前的版本，是可以按照上面的思路来的，但是之后接口做了一些改变，我这里不再赘述了。&lt;/p&gt;
&lt;p&gt;当然如果你愿意，你可以参考可靠消息最终一致性方案来自己实现一套分布式事务，比如基于 RocketMQ 来玩儿。&lt;/p&gt;
&lt;h2 id=&quot;分布式会话&quot;&gt;&lt;a href=&quot;#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BC%9A%E8%AF%9D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;分布式会话&lt;/h2&gt;
&lt;h3 id=&quot;集群分布式-session-如何实现？&quot;&gt;&lt;a href=&quot;#%E9%9B%86%E7%BE%A4%E5%88%86%E5%B8%83%E5%BC%8F-session-%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;集群分布式 Session 如何实现？&lt;/h3&gt;
&lt;p&gt;面试官问了你一堆 Dubbo 是怎么玩儿的，你会玩儿 Dubbo 就可以把单块系统弄成分布式系统，然后分布式之后接踵而来的就是一堆问题，最大的问题就是分布式事务、接口幂等性、分布式锁，还有最后一个就是分布式 Session。&lt;/p&gt;
&lt;p&gt;当然了，分布式系统中的问题何止这么一点，非常之多，复杂度很高，这里只是说一下常见的几个问题，也是面试的时候常问的几个。&lt;/p&gt;
&lt;p&gt;Session 是啥？浏览器有个 Cookie，在一段时间内这个 Cookie 都存在，然后每次发请求过来都带上一个特殊的 &lt;code class=&quot;language-text&quot;&gt;jsessionid cookie&lt;/code&gt;，就根据这个东西，在服务端可以维护一个对应的 Session 域，里面可以放点数据。&lt;/p&gt;
&lt;p&gt;一般的话只要你没关掉浏览器，Cookie 还在，那么对应的那个 Session 就在，但是如果 Cookie 没了，Session 也就没了。常见于什么购物车之类的东西，还有登录状态保存之类的。&lt;/p&gt;
&lt;p&gt;这个不多说了，懂 Java 的都该知道这个。&lt;/p&gt;
&lt;p&gt;单块系统的时候这么玩儿 Session 没问题，但是你要是分布式系统呢，那么多的服务，Session 状态在哪儿维护啊？&lt;/p&gt;
&lt;p&gt;其实方法很多，但是常见常用的是以下几种：&lt;/p&gt;
&lt;h4 id=&quot;完全不用-session&quot;&gt;&lt;a href=&quot;#%E5%AE%8C%E5%85%A8%E4%B8%8D%E7%94%A8-session&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;完全不用 Session&lt;/h4&gt;
&lt;p&gt;使用 JWT Token 储存用户身份，然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。&lt;/p&gt;
&lt;h4 id=&quot;tomcat--redis&quot;&gt;&lt;a href=&quot;#tomcat--redis&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tomcat + Redis&lt;/h4&gt;
&lt;p&gt;这个其实还挺方便的，就是使用 Session 的代码，跟以前一样，还是基于 Tomcat 原生的 Session 支持即可，然后就是用一个叫做 Tomcat RedisSessionManager 的东西，让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。&lt;/p&gt;
&lt;p&gt;在 Tomcat 的配置文件中配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26079648735766180000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;Valve className=&amp;quot;com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve&amp;quot; /&gt;

&lt;Manager className=&amp;quot;com.orangefunction.tomcat.redissessions.RedisSessionManager&amp;quot;
         host=&amp;quot;{redis.host}&amp;quot;
         port=&amp;quot;{redis.port}&amp;quot;
         database=&amp;quot;{redis.dbnum}&amp;quot;
         maxInactiveInterval=&amp;quot;60&amp;quot;/&gt;`, `26079648735766180000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Valve&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Manager&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.orangefunction.tomcat.redissessions.RedisSessionManager&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{redis.host}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{redis.port}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{redis.dbnum}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
         &lt;span class=&quot;token attr-name&quot;&gt;maxInactiveInterval&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;60&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后指定 Redis 的 host 和 port 就 ok 了。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43936080113945570000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;Valve className=&amp;quot;com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve&amp;quot; /&gt;
&lt;Manager className=&amp;quot;com.orangefunction.tomcat.redissessions.RedisSessionManager&amp;quot;
     sentinelMaster=&amp;quot;mymaster&amp;quot;
     sentinels=&amp;quot;&lt;sentinel1-ip&gt;:26379,&lt;sentinel2-ip&gt;:26379,&lt;sentinel3-ip&gt;:26379&amp;quot;
     maxInactiveInterval=&amp;quot;60&amp;quot;/&gt;`, `43936080113945570000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Valve&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Manager&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.orangefunction.tomcat.redissessions.RedisSessionManager&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
     &lt;span class=&quot;token attr-name&quot;&gt;sentinelMaster&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mymaster&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
     &lt;span class=&quot;token attr-name&quot;&gt;sentinels&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&amp;lt;sentinel1-ip&gt;:26379,&amp;lt;sentinel2-ip&gt;:26379,&amp;lt;sentinel3-ip&gt;:26379&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
     &lt;span class=&quot;token attr-name&quot;&gt;maxInactiveInterval&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;60&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;还可以用上面这种方式基于 Redis 哨兵支持的 Redis 高可用集群来保存 Session 数据，都是 ok 的。&lt;/p&gt;
&lt;h4 id=&quot;spring-session--redis&quot;&gt;&lt;a href=&quot;#spring-session--redis&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Spring Session + Redis&lt;/h4&gt;
&lt;p&gt;上面所说的第二种方式会与 Tomcat 容器重耦合，如果我要将 Web 容器迁移成 Jetty，难道还要重新把 Jetty 都配置一遍？&lt;/p&gt;
&lt;p&gt;因为上面那种 Tomcat + Redis 的方式好用，但是会严重依赖于 Web 容器，不好将代码移植到其他 Web 容器上去，尤其是你要是换了技术栈咋整？比如换成了 Spring Cloud 或者是 Spring Boot 之类的呢？&lt;/p&gt;
&lt;p&gt;所以现在比较好的还是基于 Java 一站式解决方案，也就是 Spring。人家 Spring 基本上承包了大部分我们需要使用的框架，Spirng Cloud 做微服务，Spring Boot 做脚手架，所以用 Spring Session 是一个很好的选择。&lt;/p&gt;
&lt;p&gt;在 pom.xml 中配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;18945681940844274000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.session&lt;/groupId&gt;
  &lt;artifactId&gt;spring-session-data-redis&lt;/artifactId&gt;
  &lt;version&gt;1.2.1.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;redis.clients&lt;/groupId&gt;
  &lt;artifactId&gt;jedis&lt;/artifactId&gt;
  &lt;version&gt;2.8.1&lt;/version&gt;
&lt;/dependency&gt;`, `18945681940844274000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.springframework.session&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;spring-session-data-redis&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1.2.1.RELEASE&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;redis.clients&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;jedis&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2.8.1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 Spring 配置文件中配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;38720746191937100000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;bean id=&amp;quot;redisHttpSessionConfiguration&amp;quot;
     class=&amp;quot;org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration&amp;quot;&gt;
    &lt;property name=&amp;quot;maxInactiveIntervalInSeconds&amp;quot; value=&amp;quot;600&amp;quot;/&gt;
&lt;/bean&gt;

&lt;bean id=&amp;quot;jedisPoolConfig&amp;quot; class=&amp;quot;redis.clients.jedis.JedisPoolConfig&amp;quot;&gt;
    &lt;property name=&amp;quot;maxTotal&amp;quot; value=&amp;quot;100&amp;quot; /&gt;
    &lt;property name=&amp;quot;maxIdle&amp;quot; value=&amp;quot;10&amp;quot; /&gt;
&lt;/bean&gt;

&lt;bean id=&amp;quot;jedisConnectionFactory&amp;quot;
      class=&amp;quot;org.springframework.data.redis.connection.jedis.JedisConnectionFactory&amp;quot; destroy-method=&amp;quot;destroy&amp;quot;&gt;
    &lt;property name=&amp;quot;hostName&amp;quot; value=&amp;quot;\${redis_hostname}&amp;quot;/&gt;
    &lt;property name=&amp;quot;port&amp;quot; value=&amp;quot;\${redis_port}&amp;quot;/&gt;
    &lt;property name=&amp;quot;password&amp;quot; value=&amp;quot;\${redis_pwd}&amp;quot; /&gt;
    &lt;property name=&amp;quot;timeout&amp;quot; value=&amp;quot;3000&amp;quot;/&gt;
    &lt;property name=&amp;quot;usePool&amp;quot; value=&amp;quot;true&amp;quot;/&gt;
    &lt;property name=&amp;quot;poolConfig&amp;quot; ref=&amp;quot;jedisPoolConfig&amp;quot;/&gt;
&lt;/bean&gt;`, `38720746191937100000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;bean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;redisHttpSessionConfiguration&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
     &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;maxInactiveIntervalInSeconds&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;600&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;bean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;jedisPoolConfig&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;redis.clients.jedis.JedisPoolConfig&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;maxTotal&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;maxIdle&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;bean&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;jedisConnectionFactory&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;org.springframework.data.redis.connection.jedis.JedisConnectionFactory&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;destroy-method&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;destroy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;hostName&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;${redis_hostname}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;port&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;${redis_port}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;${redis_pwd}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;timeout&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;3000&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;usePool&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;property&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;poolConfig&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;jedisPoolConfig&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 web.xml 中配置：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53612570538692950000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;filter&gt;
    &lt;filter-name&gt;springSessionRepositoryFilter&lt;/filter-name&gt;
    &lt;filter-class&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
    &lt;filter-name&gt;springSessionRepositoryFilter&lt;/filter-name&gt;
    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;`, `53612570538692950000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;xml&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;xml&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-xml line-numbers&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;springSessionRepositoryFilter&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter-class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter-class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter-mapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;filter-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;springSessionRepositoryFilter&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;url-pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;/*&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;url-pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;filter-mapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;38519098959542550000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@RestController
@RequestMapping(&amp;quot;/test&amp;quot;)
public class TestController {

    @RequestMapping(&amp;quot;/putIntoSession&amp;quot;)
    public String putIntoSession(HttpServletRequest request, String username) {
        request.getSession().setAttribute(&amp;quot;name&amp;quot;,  &amp;quot;leo&amp;quot;);
        return &amp;quot;ok&amp;quot;;
    }

    @RequestMapping(&amp;quot;/getFromSession&amp;quot;)
    public String getFromSession(HttpServletRequest request, Model model){
        String name = request.getSession().getAttribute(&amp;quot;name&amp;quot;);
        return name;
    }
}`, `38519098959542550000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/putIntoSession&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;putIntoSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;leo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/getFromSession&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFromSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpServletRequest&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Model&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上面的代码就是 ok 的，给 Spring Session 配置基于 Redis 来存储 Session 数据，然后配置了一个 Spring Session 的过滤器，这样的话，Session 相关操作都会交给 Spring Session 来管了。接着在代码中，就用原生的 Session 操作，就是直接基于 Spring Session 从 Redis 中获取数据了。&lt;/p&gt;
&lt;p&gt;实现分布式的会话有很多种方式，我说的只不过是比较常见的几种方式，Tomcat + Redis 早期比较常用，但是会重耦合到 Tomcat 中；近些年，通过 Spring Session 来实现。&lt;/p&gt;
&lt;h1 id=&quot;高可用架构&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高可用架构&lt;/h1&gt;
&lt;h2 id=&quot;基于-hystrix-实现高可用&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-hystrix-%E5%AE%9E%E7%8E%B0%E9%AB%98%E5%8F%AF%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 Hystrix 实现高可用&lt;/h2&gt;
&lt;h3 id=&quot;hystrix-介绍&quot;&gt;&lt;a href=&quot;#hystrix-%E4%BB%8B%E7%BB%8D&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 介绍&lt;/h3&gt;
&lt;h4 id=&quot;hystrix-是什么？&quot;&gt;&lt;a href=&quot;#hystrix-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 是什么？&lt;/h4&gt;
&lt;p&gt;在分布式系统中，每个服务可能会调用很多其他服务，被调用的那些服务就是依赖服务，有的时候某些依赖服务出现故障也是很正常的。&lt;/p&gt;
&lt;p&gt;Hystrix 可以让我们在分布式系统中对服务间的调用进行控制，加入一些调用延迟或者依赖故障的容错机制。&lt;/p&gt;
&lt;p&gt;Hystrix 通过将依赖服务进行资源隔离，进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延；同时 Hystrix 还提供故障时的 fallback 降级机制。&lt;/p&gt;
&lt;p&gt;总而言之，Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。&lt;/p&gt;
&lt;h4 id=&quot;hystrix-的历史&quot;&gt;&lt;a href=&quot;#hystrix-%E7%9A%84%E5%8E%86%E5%8F%B2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 的历史&lt;/h4&gt;
&lt;p&gt;Hystrix 是高可用性保障的一个框架。Netflix（可以认为是国外的优酷或者爱奇艺之类的视频网站）的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作，Hystrix 就是从那时候开始发展出来的。&lt;/p&gt;
&lt;p&gt;在 2012 年的时候，Hystrix 就变得比较成熟和稳定了，Netflix 中，除了 API 团队以外，很多其他的团队都开始使用 Hystrix。&lt;/p&gt;
&lt;p&gt;时至今日，Netflix 中每天都有数十亿次的服务间调用，通过 Hystrix 框架在进行，而 Hystrix 也帮助 Netflix 网站提升了整体的可用性和稳定性。&lt;/p&gt;
&lt;p&gt;2018 年 11 月，Hystrix 在其 Github 主页宣布，不再开放新功能，推荐开发者使用其他仍然活跃的开源项目。维护模式的转变绝不意味着 Hystrix 不再有价值。相反，Hystrix 激发了很多伟大的想法和项目，我们高可用的这一块知识还是会针对 Hystrix 进行讲解。&lt;/p&gt;
&lt;h4 id=&quot;hystrix-的设计原则&quot;&gt;&lt;a href=&quot;#hystrix-%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 的设计原则&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护。&lt;/li&gt;
&lt;li&gt;在复杂的分布式系统中，阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了，导致其它服务也跟着故障。&lt;/li&gt;
&lt;li&gt;提供 fail-fast（快速失败）和快速恢复的支持。&lt;/li&gt;
&lt;li&gt;提供 fallback 优雅降级的支持。&lt;/li&gt;
&lt;li&gt;支持近实时的监控、报警以及运维操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举个栗子。&lt;/p&gt;
&lt;p&gt;有这样一个分布式系统，服务 A 依赖于服务 B，服务 B 依赖于服务 C / D / E。在这样一个成熟的系统内，比如说最多可能只有 100 个线程资源。正常情况下，40 个线程并发调用服务 C，各 30 个线程并发调用 D / E。&lt;/p&gt;
&lt;p&gt;调用服务 C，只需要 20ms，现在因为服务 C 故障了，比如延迟，或者挂了，此时线程会 hang 住 2s 左右。40 个线程全部被卡住，由于请求不断涌入，其它的线程也用来调用服务 C，同样也会被卡住。这样导致服务 B 的线程资源被耗尽，无法接收新的请求，甚至可能因为大量线程不断的运转，导致自己宕机。这种影响势必会蔓延至服务 A，导致服务 A 也跟着挂掉。&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;graph TB
   服务A --&gt; 服务B
   服务B -- X --&gt; 服务C
   服务B --&gt; 服务D
   服务B --&gt; 服务E&lt;/div&gt;
&lt;p&gt;Hystrix 可以对其进行资源隔离，比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时，其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。&lt;/p&gt;
&lt;h4 id=&quot;hystrix-更加细节的设计原则&quot;&gt;&lt;a href=&quot;#hystrix-%E6%9B%B4%E5%8A%A0%E7%BB%86%E8%8A%82%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 更加细节的设计原则&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;阻止任何一个依赖服务耗尽所有的资源，比如 tomcat 中的所有线程资源。&lt;/li&gt;
&lt;li&gt;避免请求排队和积压，采用限流和 fail fast 来控制故障。&lt;/li&gt;
&lt;li&gt;提供 fallback 降级机制来应对故障。&lt;/li&gt;
&lt;li&gt;使用资源隔离技术，比如 bulkhead（舱壁隔离技术）、swimlane（泳道技术）、circuit breaker（断路技术）来限制任何一个依赖服务的故障的影响。&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;h3 id=&quot;电商网站详情页系统架构&quot;&gt;&lt;a href=&quot;#%E7%94%B5%E5%95%86%E7%BD%91%E7%AB%99%E8%AF%A6%E6%83%85%E9%A1%B5%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;电商网站详情页系统架构&lt;/h3&gt;
&lt;h4 id=&quot;小型电商网站的商品详情页系统架构&quot;&gt;&lt;a href=&quot;#%E5%B0%8F%E5%9E%8B%E7%94%B5%E5%95%86%E7%BD%91%E7%AB%99%E7%9A%84%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E9%A1%B5%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;小型电商网站的商品详情页系统架构&lt;/h4&gt;
&lt;p&gt;小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息，页面静态化系统，将数据填充进静态模板中，形成静态化页面，推入 Nginx 服务器。用户浏览网站页面时，取用一个已经静态化好的 html 页面，直接返回回去，不涉及任何的业务逻辑处理。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-25-15-33-55-9c45b54b8bb04320fa47e632a04be648-700d4.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 540px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 66.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABOElEQVQoz42S6YqDQBCEff+HM6DGgAde0XifUVdE3Q8HwhIFt38MY09XdXW10vsQYRjWdV1VVRAEjuP4vs+9aZooitq27fv+UykdwaKIatd17/c7J1zkX68XyQtwuwcA27YNw4Cr6zqR/6o8AcMdx3GapiDLsnw8Hoqi0P9YeQKmD8ht26ZpSpIEPOJR8TXwOViMl+d5lmWY53keYDL/kv0ZDwkYjoqiKMTY12CqaSv2hFpWhWfZHteycYs+qMUw0zQty0KzMGIYhu5PSMMhWBJN8Am1mqbBxeQIoT/U4zj+7MFFgg9L8z3ERbxhtdgZGMDQoQU6avw9SEpCDGuEGzIUUs0JTNd1tC3LMs8zZc/nEyRTqKrKE3ZIpJjtdrvJssyE8CGYnxnxMCKBha/ryidI8kjgwgndLzvt1Dm9rUTlAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 25 15 33 55&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-25-15-33-55-9c45b54b8bb04320fa47e632a04be648-700d4.png&quot; data-srcset=&quot;/static/2023-09-25-15-33-55-9c45b54b8bb04320fa47e632a04be648-555fa.png 200w,
/static/2023-09-25-15-33-55-9c45b54b8bb04320fa47e632a04be648-eb4c6.png 400w,
/static/2023-09-25-15-33-55-9c45b54b8bb04320fa47e632a04be648-700d4.png 540w&quot; data-sizes=&quot;(max-width: 540px) 100vw, 540px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;下面是页面模板的简单 Demo 。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22548894717388410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`&lt;html&gt;
   &lt;body&gt;
      商品名称：#{productName}&lt;br /&gt;
      商品价格：#{productPrice}&lt;br /&gt;
      商品描述：#{productDesc}
   &lt;/body&gt;
&lt;/html&gt;`, `22548894717388410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;html&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;html&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;html&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-html line-numbers&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
   &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      商品名称：#{productName}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      商品价格：#{productPrice}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;br&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      商品描述：#{productDesc}
   &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样做，好处在于，用户每次浏览一个页面，不需要进行任何的跟数据库的交互逻辑，也不需要执行任何的代码，直接返回一个 html 页面就可以了，速度和性能非常高。&lt;/p&gt;
&lt;p&gt;对于小网站，页面很少，很实用，非常简单，Java 中可以使用 velocity、freemarker、thymeleaf 等等，然后做个 cms 页面内容管理系统，模板变更的时候，点击按钮或者系统自动化重新进行全量渲染。&lt;/p&gt;
&lt;p&gt;坏处在于，仅仅适用于一些小型的网站，比如页面的规模在几十到几万不等。对于一些大型的电商网站，亿级数量的页面，你说你每次页面模板修改了，都需要将这么多页面全量静态化，靠谱吗？每次渲染花个好几天时间，那你整个网站就废掉了。&lt;/p&gt;
&lt;h4 id=&quot;大型电商网站的商品详情页系统架构&quot;&gt;&lt;a href=&quot;#%E5%A4%A7%E5%9E%8B%E7%94%B5%E5%95%86%E7%BD%91%E7%AB%99%E7%9A%84%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E9%A1%B5%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;大型电商网站的商品详情页系统架构&lt;/h4&gt;
&lt;p&gt;大型电商网站商品详情页的系统设计中，当商品数据发生变更时，会将变更消息压入 MQ 消息队列中。缓存服务从消息队列中消费这条消息时，感知到有数据发生变更，便通过调用数据服务接口，获取变更后的数据，然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的，比如说 10 分钟，当数据过期之后，它就会从 redis 获取到最新的缓存数据，并且缓存到自己本地。&lt;/p&gt;
&lt;p&gt;用户浏览网页时，动态将 Nginx 本地数据渲染到本地 html 模板并返回给用户。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-25-15-34-56-72f41c6acfbd0b1d5bd475c62fff3756-efa6e.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 603px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 94.02985074626866%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAACXBIWXMAAAsSAAALEgHS3X78AAABp0lEQVQ4y4XU2W7iYAwFYN7/uZC4A4GQEEJiK/s27Az7fI3bDEpT4YvEf+LjYx87KTwTW61Wu93uI7HD4cDf7/eDwaBcLo9Go/v9/ng8nj+sELfz+Xy5XESIu16vfOk6nU6xWJSOv1gsbrebtzngsM1ms91uRczn82aziXM2m02n08lk0u12FSKFsLSKL7AiIZfL5Xq9FoqzVCo1Gg1x4/G41+t1EuPngF8Nszrr9Tra0+nkKh1+hUj9Bhyd/0lMNAmev1gOWLQK2+12q9VS+fF4fGV7A071Z2ZGpH1iGal/LTsmr08K0ZlgUsiVBT8Sy2XGRi1zfsMML/rvi9kTw8Pc7/cp55jTMz2E6o2wBnN8MWM3HmVzftb8Ca5Wq5VKpVarQWZU5Stbw7G5OWC0stpK34NzkJuWTXaNrdZwLE9YdkmkAJOFPMPhULepWvZc5frafpsuAl+IW/CfEssMDD6jc3w8/8H0xACJUIdq9lzZeonQUBuBVyE++wJrzEr46GL742cApgUPRac18w0Pk7BCWkn8SYD5kI5oHT0Ux3elJTlCCDH/AKSJRp790jHbAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 25 15 34 56&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-25-15-34-56-72f41c6acfbd0b1d5bd475c62fff3756-efa6e.png&quot; data-srcset=&quot;/static/2023-09-25-15-34-56-72f41c6acfbd0b1d5bd475c62fff3756-b4d75.png 200w,
/static/2023-09-25-15-34-56-72f41c6acfbd0b1d5bd475c62fff3756-f2144.png 400w,
/static/2023-09-25-15-34-56-72f41c6acfbd0b1d5bd475c62fff3756-efa6e.png 603w&quot; data-sizes=&quot;(max-width: 603px) 100vw, 603px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;虽然没有直接返回 html 页面那么快，但是因为数据在本地缓存，所以也很快，其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更，不需要将所有的页面重新静态化，也不需要发送请求，没有网络请求的开销，直接将数据渲染进最新的 html 页面模板后响应即可。&lt;/p&gt;
&lt;p&gt;在这种架构下，我们需要保证系统的高可用性。&lt;/p&gt;
&lt;p&gt;如果系统访问量很高，Nginx 本地缓存过期失效了，redis 中的缓存也被 LRU 算法给清理掉了，那么会有较高的访问量，从缓存服务调用商品服务。但如果此时商品服务的接口发生故障，调用出现了延时，缓存服务全部的线程都被这个调用商品服务接口给耗尽了，每个线程去调用商品服务接口的时候，都会卡住很长时间，后面大量的请求过来都会卡在那儿，此时缓存服务没有足够的线程去调用其它一些服务的接口，从而导致整个大量的商品详情页无法正常显示。&lt;/p&gt;
&lt;p&gt;这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。&lt;/p&gt;
&lt;h3 id=&quot;基于-hystrix-线程池技术实现资源隔离&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-hystrix-%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%8A%80%E6%9C%AF%E5%AE%9E%E7%8E%B0%E8%B5%84%E6%BA%90%E9%9A%94%E7%A6%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 Hystrix 线程池技术实现资源隔离&lt;/h3&gt;
&lt;p&gt;上一讲提到，如果从 Nginx 开始，缓存都失效了，Nginx 会直接通过缓存服务调用商品服务获取最新商品数据（我们基于电商项目做个讨论），有可能出现调用延时而把缓存服务资源耗尽的情况。这里，我们就来说说，怎么通过 Hystrix 线程池技术实现资源隔离。&lt;/p&gt;
&lt;p&gt;资源隔离，就是说，你如果要把对某一个依赖服务的所有调用请求，全部隔离在同一份资源池内，不会去用其它资源了，这就叫资源隔离。哪怕对这个依赖服务，比如说商品服务，现在同时发起的调用量已经到了 1000，但是分配给商品服务线程池内就 10 个线程，最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟，将 Tomcat 内部所有的线程资源全部耗尽。&lt;/p&gt;
&lt;p&gt;Hystrix 进行资源隔离，其实是提供了一个抽象，叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。&lt;/p&gt;
&lt;h4 id=&quot;利用-hystrixcommand-获取单条数据&quot;&gt;&lt;a href=&quot;#%E5%88%A9%E7%94%A8-hystrixcommand-%E8%8E%B7%E5%8F%96%E5%8D%95%E6%9D%A1%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;利用 HystrixCommand 获取单条数据&lt;/h4&gt;
&lt;p&gt;我们通过将调用商品服务的操作封装在 HystrixCommand 中，限定一个 key，比如下面的 GetProductInfoCommandGroup，在这里我们可以简单认为这是一个线程池，每次调用商品服务，就只会用该线程池中的资源，不会再去用其它线程资源了。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;35604876517840278000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfoCommand extends HystrixCommand&lt;ProductInfo&gt; {

    private Long productId;

    public GetProductInfoCommand(Long productId) {
        super(HystrixCommandGroupKey.Factory.asKey(&amp;quot;GetProductInfoCommandGroup&amp;quot;));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() {
        String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
        // 调用商品服务接口
        String response = HttpClientUtils.sendGetRequest(url);
        return JSONObject.parseObject(response, ProductInfo.class);
    }
}`, `35604876517840278000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoCommandGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 调用商品服务接口&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们在缓存服务接口中，根据 productId 创建 Command 并执行，获取到商品数据。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22446488006217160000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@RequestMapping(&amp;quot;/getProductInfo&amp;quot;)
@ResponseBody
public String getProductInfo(Long productId) {
    HystrixCommand&lt;ProductInfo&gt; getProductInfoCommand = new GetProductInfoCommand(productId);

    // 通过 command 执行，获取最新商品数据
    ProductInfo productInfo = getProductInfoCommand.execute();
    System.out.println(productInfo);
    return &amp;quot;success&amp;quot;;
}`, `22446488006217160000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/getProductInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseBody&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; getProductInfoCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 通过 command 执行，获取最新商品数据&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;上面执行的是 execute() 方法，其实是同步的。也可以对 command 调用 queue() 方法，它仅仅是将 command 放入线程池的一个等待队列，就立即返回，拿到一个 Future 对象，后面可以继续做其它一些事情，然后过一段时间对 Future 调用 get() 方法获取数据。这是异步的。&lt;/p&gt;
&lt;h4 id=&quot;利用-hystrixobservablecommand-批量获取数据&quot;&gt;&lt;a href=&quot;#%E5%88%A9%E7%94%A8-hystrixobservablecommand-%E6%89%B9%E9%87%8F%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;利用 HystrixObservableCommand 批量获取数据&lt;/h4&gt;
&lt;p&gt;只要是获取商品数据，全部都绑定到同一个线程池里面去，我们通过 HystrixObservableCommand 的一个线程去执行，而在这个线程里面，批量把多个 productId 的 productInfo 拉回来。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;17206337217255907000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfosCommand extends HystrixObservableCommand&lt;ProductInfo&gt; {

    private String[] productIds;

    public GetProductInfosCommand(String[] productIds) {
        // 还是绑定在同一个线程池
        super(HystrixCommandGroupKey.Factory.asKey(&amp;quot;GetProductInfoGroup&amp;quot;));
        this.productIds = productIds;
    }

    @Override
    protected Observable&lt;ProductInfo&gt; construct() {
        return Observable.unsafeCreate((Observable.OnSubscribe&lt;ProductInfo&gt;) subscriber -&gt; {

            for (String productId : productIds) {
                // 批量获取商品数据
                String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
                String response = HttpClientUtils.sendGetRequest(url);
                ProductInfo productInfo = JSONObject.parseObject(response, ProductInfo.class);
                subscriber.onNext(productInfo);
            }
            subscriber.onCompleted();

        }).subscribeOn(Schedulers.io());
    }
}`, `17206337217255907000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfosCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixObservableCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfosCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 还是绑定在同一个线程池&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productIds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;construct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unsafeCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OnSubscribe&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; subscriber &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productId &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 批量获取商品数据&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            subscriber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribeOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Schedulers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在缓存服务接口中，根据传来的 id 列表，比如是以 &lt;code class=&quot;language-text&quot;&gt;,&lt;/code&gt; 分隔的 id 串，通过上面的 HystrixObservableCommand，执行 Hystrix 的一些 API 方法，获取到所有商品数据。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;4109003169845504500&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public String getProductInfos(String productIds) {
    String[] productIdArray = productIds.split(&amp;quot;,&amp;quot;);
    HystrixObservableCommand&lt;ProductInfo&gt; getProductInfosCommand = new GetProductInfosCommand(productIdArray);
    Observable&lt;ProductInfo&gt; observable = getProductInfosCommand.observe();

    observable.subscribe(new Observer&lt;ProductInfo&gt;() {
        @Override
        public void onCompleted() {
            System.out.println(&amp;quot;获取完了所有的商品数据&amp;quot;);
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        /**
         * 获取完一条数据，就回调一次这个方法
         * @param productInfo
         */
        @Override
        public void onNext(ProductInfo productInfo) {
            System.out.println(productInfo);
        }
    });
    return &amp;quot;success&amp;quot;;
}`, `4109003169845504500`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProductInfos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; productIdArray &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HystrixObservableCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; getProductInfosCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfosCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productIdArray&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; observable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfosCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    observable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Observer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;获取完了所有的商品数据&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;/**
         * 获取完一条数据，就回调一次这个方法
         * @param productInfo
         */&lt;/span&gt;
        &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们回过头来，看看 Hystrix 线程池技术是如何实现资源隔离的。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-27-09-44-13-24ac103f7f1e73b648e7a54f15738bbe-344e0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 307px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 111.40065146579805%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAIAAABPIytRAAAACXBIWXMAAAsSAAALEgHS3X78AAAB3klEQVQ4y5WUWXOCUAyF+f9/SN9d0AcdRcQVF1xQ7FgFcUf6QWprp1PuNDNecyEnOTmJavv93nGcdrs9m80Gg0G32+WcTCa2bfd6vdPpFATB4Q/TOp1Oq9UCH8cxiciyXq/x7/c7+DAMs8CkP5/Pw+GQ0NFoZD+N+mQ8Ho9ZYHgCJij8ZXA+ZNpnZZqcTqe0vVgsOKnJk+VyqagMmNfwpM52uwW82WzA+L4PcwW43+8TBwaRCKVt13VFsPF4rBYMDJ0LbajO53Nog0RFtWCXyyV4Giy+fMoqBBPwbrej4fefllHzG0x7nHB2U4P5arWyLAtHpoiWMkvx5UrqBIyH2oh0vV5hcbvd8N9SI0Jy4Qs79o8rJykSwXK5XLlcBlCv1yuVSj6f5yHNs6okJQ5ks9kkrFgsQs3zPKYDNQ3qMufH4wGeK/xZcvCGYRQKBZQnFILVarXRaPBWxsGZgCEAmAhSshi1Wg0YJwV5RSJY0DD9c/VTo15CGzAv6Ac+7BZkJAs+Qa/ayvxer5p4IiC0S6USWx1FEU/Uc+YDDAD7RCe6rpumiUOTsuRZGya06Zlpx6mJcjCnEcU/iXyxyQD4VTAe1GKeosI/wFSTHzbCgmcZ1GBoU5M4mEdPo6wS/AExkscjVCm1sQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 27 09 44 13&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-27-09-44-13-24ac103f7f1e73b648e7a54f15738bbe-344e0.png&quot; data-srcset=&quot;/static/2023-09-27-09-44-13-24ac103f7f1e73b648e7a54f15738bbe-73b08.png 200w,
/static/2023-09-27-09-44-13-24ac103f7f1e73b648e7a54f15738bbe-344e0.png 307w&quot; data-sizes=&quot;(max-width: 307px) 100vw, 307px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从 Nginx 开始，缓存都失效了，那么 Nginx 通过缓存服务去调用商品服务。缓存服务默认的线程大小是 10 个，最多就只有 10 个线程去调用商品服务的接口。即使商品服务接口故障了，最多就只有 10 个线程会 hang 死在调用商品服务接口的路上，缓存服务的 Tomcat 内其它的线程还是可以用来调用其它的服务，干其它的事情。&lt;/p&gt;
&lt;h3 id=&quot;基于-hystrix-信号量机制实现资源隔离&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-hystrix-%E4%BF%A1%E5%8F%B7%E9%87%8F%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%B5%84%E6%BA%90%E9%9A%94%E7%A6%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 Hystrix 信号量机制实现资源隔离&lt;/h3&gt;
&lt;p&gt;Hystrix 里面核心的一项功能，其实就是所谓的资源隔离，要解决的最最核心的问题，就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用，因为依赖服务的接口调用的延迟或者失败，导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话，就可能导致服务崩溃，甚至说这种故障会不断蔓延。&lt;/p&gt;
&lt;p&gt;Hystrix 实现资源隔离，主要有两种技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程池&lt;/li&gt;
&lt;li&gt;信号量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;默认情况下，Hystrix 使用线程池模式。&lt;/p&gt;
&lt;p&gt;前面已经说过线程池技术了，这一小节就来说说信号量机制实现资源隔离，以及这两种技术的区别与具体应用场景。&lt;/p&gt;
&lt;h4 id=&quot;信号量机制&quot;&gt;&lt;a href=&quot;#%E4%BF%A1%E5%8F%B7%E9%87%8F%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;信号量机制&lt;/h4&gt;
&lt;p&gt;信号量的资源隔离只是起到一个开关的作用，比如，服务 A 的信号量大小为 10，那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A，其它的请求都会被拒绝，从而达到资源隔离和限流保护的作用。&lt;/p&gt;
&lt;h4 id=&quot;线程池与信号量区别&quot;&gt;&lt;a href=&quot;#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%B8%8E%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%8C%BA%E5%88%AB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;线程池与信号量区别&lt;/h4&gt;
&lt;p&gt;线程池隔离技术，并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说，Hystrix 的线程池隔离技术，控制的是 tomcat 线程的执行。Hystrix 线程池满后，会确保说，tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住，tomcat 其它的线程不会卡死，可以快速返回，然后支撑其它的事情。&lt;/p&gt;
&lt;p&gt;线程池隔离技术，是用 Hystrix 自己的线程去执行调用；而信号量隔离技术，是直接让 tomcat 线程去调用依赖服务。信号量隔离，只是一道关卡，信号量有多少，就允许多少个 tomcat 线程通过它，然后去执行。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-27-09-47-04-f8d7a1db585c2d3f7c694a2a3625a9ec-344e0.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 307px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 61.56351791530944%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsSAAALEgHS3X78AAABdklEQVQoz2WSWY+CUAyF+f+/xzdeMEbcQjDgvqEQIS4sZhRxZb6xrmMfbnrbc3ra26v0ej3P82af5rouwclk0m63W61Wp9NxbyYpyQ6HQ2U6neZ5fr1e8y87nU7L5TIIgiiK3uMCJqVQDC+OY3TwB4MBOs1mU9f1brf7Tnja5XL5IB+PxyzLdrtdGIaHw+HnZkQEKlJEttstGJS4rlarO1lK4qAPaL1eP+PP0/d95jRNs9/v35W5bzabJEkYLE1T27ZJj0ajRqNBO4jQy/l8Bu04jqqqhUJB07Q7uVKpoEOCB4RjWRaj2jczDIP4YrGgQ8TpBQL6cv61XavVmGo8HrMVyDCJIItfrVYJ0hfVEZ/P5wxVr9fBwKSoUi6XGRIofE52iIMgzw4Ih8enc2mb7lgtVRCnosInYWDKwKQZcIBw+BvIgibC8LIwnprq4r9W9f1JSqUS7/cvxQjFYvFFZgx+Uvqw/X5PeRxOfIlkD5Ml40Dh5/0CHLuUW0S2gzQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 27 09 47 04&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-27-09-47-04-f8d7a1db585c2d3f7c694a2a3625a9ec-344e0.png&quot; data-srcset=&quot;/static/2023-09-27-09-47-04-f8d7a1db585c2d3f7c694a2a3625a9ec-73b08.png 200w,
/static/2023-09-27-09-47-04-f8d7a1db585c2d3f7c694a2a3625a9ec-344e0.png 307w&quot; data-sizes=&quot;(max-width: 307px) 100vw, 307px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程池技术，适合绝大多数场景，比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制（捕捉 timeout 超时异常）。&lt;/li&gt;
&lt;li&gt;信号量技术，适合说你的访问不是对外部依赖的访问，而是对内部的一些比较复杂的业务逻辑的访问，并且系统内部的代码，其实不涉及任何的网络请求，那么只要做信号量的普通限流就可以了，因为不需要去捕获 timeout 类似的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;信号量简单-demo&quot;&gt;&lt;a href=&quot;#%E4%BF%A1%E5%8F%B7%E9%87%8F%E7%AE%80%E5%8D%95-demo&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;信号量简单 Demo&lt;/h4&gt;
&lt;p&gt;业务背景里，比较适合信号量的是什么场景呢？&lt;/p&gt;
&lt;p&gt;比如说，我们一般来说，缓存服务，可能会将一些量特别少、访问又特别频繁的数据，放在自己的纯内存中。&lt;/p&gt;
&lt;p&gt;举个栗子。一般我们在获取到商品数据之后，都要去获取商品是属于哪个地理位置、省、市、卖家等，可能在自己的纯内存中，比如就一个 Map 去获取。对于这种直接访问本地内存的逻辑，比较适合用信号量做一下简单的隔离。&lt;/p&gt;
&lt;p&gt;优点在于，不用自己管理线程池啦，不用 care timeout 超时啦，也不需要进行线程的上下文切换啦。信号量做隔离的话，性能相对来说会高一些。&lt;/p&gt;
&lt;p&gt;假如这是本地缓存，我们可以通过 cityId，拿到 cityName。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29816707521733380000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class LocationCache {
    private static Map&lt;Long, String&gt; cityMap = new HashMap&lt;&gt;();

    static {
        cityMap.put(1L, &amp;quot;北京&amp;quot;);
    }

    /**
     * 通过 cityId 获取 cityName
     *
     * @param cityId 城市 id
     * @return 城市名
     */
    public static String getCityName(Long cityId) {
        return cityMap.get(cityId);
    }
}`, `29816707521733380000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LocationCache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; cityMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        cityMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;北京&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 通过 cityId 获取 cityName
     *
     * @param cityId 城市 id
     * @return 城市名
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCityName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; cityId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cityMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cityId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写一个 GetCityNameCommand，策略设置为信号量。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;48562147821155420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetCityNameCommand extends HystrixCommand&lt;String&gt; {

    private Long cityId;

    public GetCityNameCommand(Long cityId) {
        // 设置信号量隔离策略
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;GetCityNameGroup&amp;quot;))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)));

        this.cityId = cityId;
    }

    @Override
    protected String run() {
        // 需要进行信号量隔离的代码
        return LocationCache.getCityName(cityId);
    }
}`, `48562147821155420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetCityNameCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; cityId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetCityNameCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; cityId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 设置信号量隔离策略&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetCityNameGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SEMAPHORE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cityId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cityId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 需要进行信号量隔离的代码&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LocationCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCityName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cityId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在接口层，通过创建 GetCityNameCommand，传入 cityId，执行 execute() 方法，那么获取本地 cityName 缓存的代码将会进行信号量的资源隔离。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;14373982668426244000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@RequestMapping(&amp;quot;/getProductInfo&amp;quot;)
@ResponseBody
public String getProductInfo(Long productId) {
    HystrixCommand&lt;ProductInfo&gt; getProductInfoCommand = new GetProductInfoCommand(productId);

    // 通过 command 执行，获取最新商品数据
    ProductInfo productInfo = getProductInfoCommand.execute();

    Long cityId = productInfo.getCityId();

    GetCityNameCommand getCityNameCommand = new GetCityNameCommand(cityId);
    // 获取本地内存（cityName）的代码会被信号量进行资源隔离
    String cityName = getCityNameCommand.execute();

    productInfo.setCityName(cityName);

    System.out.println(productInfo);
    return &amp;quot;success&amp;quot;;
}`, `14373982668426244000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/getProductInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseBody&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; getProductInfoCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 通过 command 执行，获取最新商品数据&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; cityId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCityId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;GetCityNameCommand&lt;/span&gt; getCityNameCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetCityNameCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cityId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取本地内存（cityName）的代码会被信号量进行资源隔离&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; cityName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getCityNameCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCityName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cityName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;hystrix-隔离策略细粒度控制&quot;&gt;&lt;a href=&quot;#hystrix-%E9%9A%94%E7%A6%BB%E7%AD%96%E7%95%A5%E7%BB%86%E7%B2%92%E5%BA%A6%E6%8E%A7%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 隔离策略细粒度控制&lt;/h3&gt;
&lt;p&gt;Hystrix 实现资源隔离，有两种策略：&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;h4 id=&quot;executionisolationstrategy&quot;&gt;&lt;a href=&quot;#executionisolationstrategy&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;execution.isolation.strategy&lt;/h4&gt;
&lt;p&gt;指定了 HystrixCommand.run() 的资源隔离策略：THREAD or SEMAPHORE，一种基于线程池，一种基于信号量。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;6043128453340785000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// to use thread isolation
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)

// to use semaphore isolation
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)`, `6043128453340785000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// to use thread isolation&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;THREAD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// to use semaphore isolation&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExecutionIsolationStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SEMAPHORE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;线程池机制，每个 command 运行在一个线程中，限流是通过线程池的大小来控制的；信号量机制，command 是运行在调用线程中（也就是 Tomcat 的线程池），通过信号量的容量来进行限流。&lt;/p&gt;
&lt;p&gt;如何在线程池和信号量之间做选择？&lt;/p&gt;
&lt;p&gt;默认的策略就是线程池。&lt;/p&gt;
&lt;p&gt;线程池其实最大的好处就是对于网络访问请求，如果有超时的话，可以避免调用线程阻塞住。&lt;/p&gt;
&lt;p&gt;而使用信号量的场景，通常是针对超大并发量的场景下，每个服务实例每秒都几百的 QPS，那么此时你用线程池的话，线程一般不会太多，可能撑不住那么高的并发，如果要撑住，可能要耗费大量的线程资源，那么就是用信号量，来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务，而不涉及到任何网络访问请求。&lt;/p&gt;
&lt;h4 id=&quot;command-key--command-group&quot;&gt;&lt;a href=&quot;#command-key--command-group&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;command key &amp;#x26; command group&lt;/h4&gt;
&lt;p&gt;我们使用线程池隔离，要怎么对依赖服务、依赖服务接口、线程池三者做划分呢？&lt;/p&gt;
&lt;p&gt;每一个 command，都可以设置一个自己的名称 command key，同时可以设置一个自己的组 command group。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58776083190763110000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ExampleGroup&amp;quot;))
                                                 .andCommandKey(HystrixCommandKey.Factory.asKey(&amp;quot;HelloWorld&amp;quot;));

public CommandHelloWorld(String name) {
    super(cachedSetter);
    this.name = name;
}`, `58776083190763110000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; cachedSetter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ExampleGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                                 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;HelloWorld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommandHelloWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cachedSetter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;command group 是一个非常重要的概念，默认情况下，就是通过 command group 来定义一个线程池的，而且还会通过 command group 来聚合一些监控和报警信息。同一个 command group 中的请求，都会进入同一个线程池中。&lt;/p&gt;
&lt;h4 id=&quot;command-thread-pool&quot;&gt;&lt;a href=&quot;#command-thread-pool&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;command thread pool&lt;/h4&gt;
&lt;p&gt;ThreadPoolKey 代表了一个 HystrixThreadPool，用来进行统一监控、统计、缓存。默认的 ThreadPoolKey 就是 command group 的名称。每个 command 都会跟它的 ThreadPoolKey 对应的 ThreadPool 绑定在一起。&lt;/p&gt;
&lt;p&gt;如果不想直接用 command group，也可以手动设置 ThreadPool 的名称。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;39349673756246100000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ExampleGroup&amp;quot;))
                                                 .andCommandKey(HystrixCommandKey.Factory.asKey(&amp;quot;HelloWorld&amp;quot;))
                                                 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(&amp;quot;HelloWorldPool&amp;quot;));

public CommandHelloWorld(String name) {
    super(cachedSetter);
    this.name = name;
}`, `39349673756246100000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt; cachedSetter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ExampleGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                                 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;HelloWorld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                                 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andThreadPoolKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixThreadPoolKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;HelloWorldPool&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommandHelloWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cachedSetter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;command-key--command-group--command-thread-pool&quot;&gt;&lt;a href=&quot;#command-key--command-group--command-thread-pool&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;command key &amp;#x26; command group &amp;#x26; command thread pool&lt;/h4&gt;
&lt;p&gt;command key 代表了一类 command，一般来说，代表了下游依赖服务的某个接口。&lt;/p&gt;
&lt;p&gt;command group 代表了某一个下游依赖服务，这是很合理的，一个依赖服务可能会暴露出来多个接口，每个接口就是一个 command key。command group 在逻辑上对一堆 command key 的调用次数、成功次数、timeout 次数、失败次数等进行统计，可以看到某一个服务整体的一些访问情况。一般来说，推荐根据一个服务区划分出一个线程池，command key 默认都是属于同一个线程池的。&lt;/p&gt;
&lt;p&gt;比如说有一个服务 A，你估算出来服务 A 每秒所有接口加起来的整体 QPS 在 100 左右，你有一个服务 B 去调用服务 A。你的服务 B 部署了 10 个实例，每个实例上，用 command group 去对应下游服务 A。给一个线程池，量大概是 10 就可以了，这样服务 B 对服务 A 整体的访问 QPS 就大概是每秒 100 了。&lt;/p&gt;
&lt;p&gt;但是，如果说 command group 对应了一个服务，而这个服务暴露出来的几个接口，访问量很不一样，差异非常之大。你可能就希望在这个服务对应 command group 的内部，包含对应多个接口的 command key，做一些细粒度的资源隔离。就是说，希望对同一个服务的不同接口，使用不同的线程池。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;41054074471665520000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`command key -&gt; command group

command key -&gt; 自己的 thread pool key`, `41054074471665520000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;command key -&amp;gt; command group

command key -&amp;gt; 自己的 thread pool key&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逻辑上来说，多个 command key 属于一个 command group，在做统计的时候，会放在一起统计。每个 command key 有自己的线程池，每个接口有自己的线程池，去做资源隔离和限流。&lt;/p&gt;
&lt;p&gt;说白点，就是说如果你的 command key 要用自己的线程池，可以定义自己的 thread pool key，就 ok 了。&lt;/p&gt;
&lt;h4 id=&quot;coresize&quot;&gt;&lt;a href=&quot;#coresize&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;coreSize&lt;/h4&gt;
&lt;p&gt;设置线程池的大小，默认是 10。一般来说，用这个默认的 10 个线程大小就够了。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;51521128261174660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixThreadPoolProperties.Setter().withCoreSize(int value);`, `51521128261174660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixThreadPoolProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCoreSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;queuesizerejectionthreshold&quot;&gt;&lt;a href=&quot;#queuesizerejectionthreshold&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;queueSizeRejectionThreshold&lt;/h4&gt;
&lt;p&gt;如果说线程池中的 10 个线程都在工作中，没有空闲的线程来做其它的事情，此时再有请求过来，会先进入队列积压。如果说队列积压满了，再有请求过来，就直接 reject，拒绝请求，执行 fallback 降级的逻辑，快速返回。&lt;/p&gt;
&lt;p&gt;控制 queue 满了之后 reject 的 threshold，因为 maxQueueSize 不允许热修改，因此提供这个参数可以热修改，控制队列的最大大小。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27577743719045087000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value);`, `27577743719045087000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixThreadPoolProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withQueueSizeRejectionThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;executionisolationsemaphoremaxconcurrentrequests&quot;&gt;&lt;a href=&quot;#executionisolationsemaphoremaxconcurrentrequests&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;execution.isolation.semaphore.maxConcurrentRequests&lt;/h4&gt;
&lt;p&gt;设置使用 SEMAPHORE 隔离策略的时候允许访问的最大并发量，超过这个最大并发量，请求直接被 reject。&lt;/p&gt;
&lt;p&gt;这个并发量的设置，跟线程池大小的设置，应该是类似的，但是基于信号量的话，性能会好很多，而且 Hystrix 框架本身的开销会小很多。&lt;/p&gt;
&lt;p&gt;默认值是 10，尽量设置的小一些，因为一旦设置的太大，而且有延时发生，可能瞬间导致 tomcat 本身的线程资源被占满。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;52518126772890310000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(int value);`, `52518126772890310000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionIsolationSemaphoreMaxConcurrentRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;深入-hystrix-执行时内部原理&quot;&gt;&lt;a href=&quot;#%E6%B7%B1%E5%85%A5-hystrix-%E6%89%A7%E8%A1%8C%E6%97%B6%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;深入 Hystrix 执行时内部原理&lt;/h3&gt;
&lt;p&gt;前面我们了解了 Hystrix 最基本的支持高可用的技术：资源隔离 + 限流。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建 command；&lt;/li&gt;
&lt;li&gt;执行这个 command；&lt;/li&gt;
&lt;li&gt;配置这个 command 对应的 group 和线程池。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里，我们要讲一下，你开始执行这个 command，调用了这个 command 的 execute() 方法之后，Hystrix 底层的执行流程和步骤以及原理是什么。&lt;/p&gt;
&lt;p&gt;在讲解这个流程的过程中，我会带出来 Hystrix 其他的一些核心以及重要的功能。&lt;/p&gt;
&lt;p&gt;这里是整个 8 大步骤的流程图，我会对每个步骤进行细致的讲解。学习的过程中，对照着这个流程图，相信思路会比较清晰。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-27-09-59-32-2b9166454fa5de7e44a005835af85350-0e897.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 578px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 160.0346020761246%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAIAAACdAM/hAAAACXBIWXMAAAsSAAALEgHS3X78AAADO0lEQVQ4y5VVSW/TQBTO/+KIKvVUUC9I8Ns4cECqEJSloVQUSmhD1tI6jpOQtKRZ7diKt/Fuj8c8e1q3cUObPkWj8cx8b/neklxwQzDGCKHpaMyxbKvJCZOpaRiEkMFgUKvVqtXqeDyGT9/3o0RymqZ5nucmAhuk6bOp8Kfd6bTaIj8DXXBo27bjOJZlwRq/QSgMwxgMF9Et8WJxo/8IQG6DSXzhWbV2ucQWS80i22cC7K8EhmBgNR2UP/zwtby7X90r/P7mYy/RSu61fC2u5zruw932bH3Y+DFtHo7ZgtSr4sBNr+4EJ267hjypvD3de1XPvxSZfOCaq4EvVeAIm54lwy/CdkTCFd1OzUey7ijIyVq8G0wJFyT13dfTzz9bsop8P874A8CypheZQZUd6cjAQWAYxqpuU3HiWvWpOijJLNh1F8DhlVDjqm7ohkXBaSVfUkCIbRghxEJIDu4EQahUKo1Gg2GYTqfNi/MiO620ZiqK82SaJqggCV4pHTEbj9lnG+yLzcbmeg5Cgk4EN/2EHAhSkOQiy5ebvKYbNMLYXohjOn4dltcelTfXmedPT5+sLY8ZmY6GTBz40IY3CcNhKEvSpN8PgLMgWA42TMu0Ei7IQqYheOhsPZkQWbYpZ4qmF+pnpcaF5bgQERiHiOicgY2u66qqZsEAg5gh7slM+Vjg3n9v8KIMlFIYnTagCMBAYTbP8A3vaEY0w5uKCvTlbDbr9/vD4ZBmm2qB/QIY3KBeUUVgwbIteDGfzyVJglzSIqV5yYIv8ckRsFXjhpXGXxh71CPA0NdZsJMIuAdjdSaKcDQRldc7pe39E0FS4CVUUezH1QBdcPsmWEzAcTGYeI78xVYl5CptywcgPQqxh3TZNNTAd4FCQsg9XRUHHAR+gCMPjWqfukfb3MHW5ORL6Fs8L5yfn4GkZpZYBloCHEa+KbcPzsr5XnlH7RWj0NN0JEkiz/PpVFgGvs5zlPBgp2OIJjKdFtdgIDAFpy8ueG0gqClPINRsFgx/dIqiQLnCBjhHujYSlK3d+pu9Y0kxbAtq0QQ36EqFdngMhlrlOK7b7Y5Go/rxca/bhU7qTdFQsmif4FuSDpZ/2UIm2YGLvMoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 27 09 59 32&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-27-09-59-32-2b9166454fa5de7e44a005835af85350-0e897.png&quot; data-srcset=&quot;/static/2023-09-27-09-59-32-2b9166454fa5de7e44a005835af85350-d60f6.png 200w,
/static/2023-09-27-09-59-32-2b9166454fa5de7e44a005835af85350-6689e.png 400w,
/static/2023-09-27-09-59-32-2b9166454fa5de7e44a005835af85350-0e897.png 578w&quot; data-sizes=&quot;(max-width: 578px) 100vw, 578px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&quot;步骤一：创建-command&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%B8%80%EF%BC%9A%E5%88%9B%E5%BB%BA-command&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤一：创建 command&lt;/h4&gt;
&lt;p&gt;一个 HystrixCommand 或 HystrixObservableCommand 对象，代表了对某个依赖服务发起的一次请求或者调用。创建的时候，可以在构造函数中传入任何需要的参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HystrixCommand 主要用于仅仅会返回一个结果的调用。&lt;/li&gt;
&lt;li&gt;HystrixObservableCommand 主要用于可能会返回多条结果的调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;2501920259952084000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 创建 HystrixCommand
HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2);

// 创建 HystrixObservableCommand
HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2);`, `2501920259952084000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 创建 HystrixCommand&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt; hystrixCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arg2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 创建 HystrixObservableCommand&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;HystrixObservableCommand&lt;/span&gt; hystrixObservableCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixObservableCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arg2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;步骤二：调用-command-执行方法&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%BA%8C%EF%BC%9A%E8%B0%83%E7%94%A8-command-%E6%89%A7%E8%A1%8C%E6%96%B9%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤二：调用 command 执行方法&lt;/h4&gt;
&lt;p&gt;执行 command，就可以发起一次对依赖服务的调用。&lt;/p&gt;
&lt;p&gt;要执行 command，可以在 4 个方法中选择其中的一个：execute()、queue()、observe()、toObservable()。&lt;/p&gt;
&lt;p&gt;其中 execute() 和 queue() 方法仅仅对 HystrixCommand 适用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;execute()&lt;/code&gt;：调用后直接 block 住，属于同步调用，直到依赖服务返回单条结果，或者抛出异常。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;queue()&lt;/code&gt;：返回一个 Future，属于异步调用，后面可以通过 Future 获取单条结果。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;observe()&lt;/code&gt;：订阅一个 Observable 对象，Observable 代表的是依赖服务返回的结果，获取到一个那个代表结果的 Observable 对象的拷贝对象。&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;toObservable()&lt;/code&gt;：返回一个 Observable 对象，如果我们订阅这个对象，就会执行 command 并且获取返回结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;44763899685021550000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`K             value    = hystrixCommand.execute();
Future&lt;K&gt;     fValue   = hystrixCommand.queue();
Observable&lt;K&gt; oValue   = hystrixObservableCommand.observe();
Observable&lt;K&gt; toOValue = hystrixObservableCommand.toObservable();`, `44763899685021550000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;             value    &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hystrixCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;     fValue   &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hystrixCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; oValue   &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hystrixObservableCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; toOValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hystrixObservableCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toObservable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;execute() 实际上会调用 &lt;code class=&quot;language-text&quot;&gt;queue().get()&lt;/code&gt; 方法，可以看一下 Hystrix 源码。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;75662622234447770000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public R execute() {
    try {
        return queue().get();
    } catch (Exception e) {
        throw Exceptions.sneakyThrow(decomposeException(e));
    }
}`, `75662622234447770000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exceptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sneakyThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decomposeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而在 queue() 方法中，会调用 &lt;code class=&quot;language-text&quot;&gt;toObservable().toBlocking().toFuture()&lt;/code&gt;。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;22699595733772804000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`final Future&lt;R&gt; delegate = toObservable().toBlocking().toFuture();`, `22699595733772804000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Future&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; delegate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toObservable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBlocking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;也就是说，先通过 toObservable() 获得 Future 对象，然后调用 Future 的 get() 方法。那么，其实无论是哪种方式执行 command，最终都是依赖于 toObservable() 去执行的。&lt;/p&gt;
&lt;h4 id=&quot;步骤三：检查是否开启缓存（不太常用）&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%B8%89%EF%BC%9A%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF%E7%BC%93%E5%AD%98%EF%BC%88%E4%B8%8D%E5%A4%AA%E5%B8%B8%E7%94%A8%EF%BC%89&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤三：检查是否开启缓存（不太常用）&lt;/h4&gt;
&lt;p&gt;从这一步开始，就进入到 Hystrix 底层运行原理啦，看一下 Hystrix 一些更高级的功能和特性。&lt;/p&gt;
&lt;p&gt;如果这个 command 开启了请求缓存 Request Cache，而且这个调用的结果在缓存中存在，那么直接从缓存中返回结果。否则，继续往后的步骤。&lt;/p&gt;
&lt;h4 id=&quot;步骤四：检查是否开启了断路器&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E5%9B%9B%EF%BC%9A%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF%E4%BA%86%E6%96%AD%E8%B7%AF%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤四：检查是否开启了断路器&lt;/h4&gt;
&lt;p&gt;检查这个 command 对应的依赖服务是否开启了断路器。如果断路器被打开了，那么 Hystrix 就不会执行这个 command，而是直接去执行 fallback 降级机制，返回降级结果。&lt;/p&gt;
&lt;h4 id=&quot;步骤五：检查线程池队列信号量是否已满&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%BA%94%EF%BC%9A%E6%A3%80%E6%9F%A5%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%98%9F%E5%88%97%E4%BF%A1%E5%8F%B7%E9%87%8F%E6%98%AF%E5%90%A6%E5%B7%B2%E6%BB%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤五：检查线程池/队列/信号量是否已满&lt;/h4&gt;
&lt;p&gt;如果这个 command 线程池和队列已满，或者 semaphore 信号量已满，那么也不会执行 command，而是直接去调用 fallback 降级机制，同时发送 reject 信息给断路器统计。&lt;/p&gt;
&lt;h4 id=&quot;步骤六：执行-command&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E5%85%AD%EF%BC%9A%E6%89%A7%E8%A1%8C-command&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤六：执行 command&lt;/h4&gt;
&lt;p&gt;调用 HystrixObservableCommand 对象的 construct() 方法，或者 HystrixCommand 的 run() 方法来实际执行这个 command。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;HystrixCommand.run()&lt;/code&gt; 返回单条结果，或者抛出异常。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;61713435029456495000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`// 通过 command 执行，获取最新一条商品数据
ProductInfo productInfo = getProductInfoCommand.execute();`, `61713435029456495000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 通过 command 执行，获取最新一条商品数据&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;HystrixObservableCommand.construct()&lt;/code&gt; 返回一个 Observable 对象，可以获取多条结果。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;27633347167168920000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`Observable&lt;ProductInfo&gt; observable = getProductInfosCommand.observe();

// 订阅获取多条结果
observable.subscribe(new Observer&lt;ProductInfo&gt;() {
  @Override
  public void onCompleted() {
     System.out.println(&amp;quot;获取完了所有的商品数据&amp;quot;);
  }

  @Override
  public void onError(Throwable e) {
     e.printStackTrace();
  }

  /**
   * 获取完一条数据，就回调一次这个方法
   *
   * @param productInfo 商品信息
   */
  @Override
  public void onNext(ProductInfo productInfo) {
     System.out.println(productInfo);
  }
});`, `27633347167168920000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Observable&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; observable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfosCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 订阅获取多条结果&lt;/span&gt;
observable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Observer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;获取完了所有的商品数据&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Throwable&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/**
   * 获取完一条数据，就回调一次这个方法
   *
   * @param productInfo 商品信息
   */&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是采用线程池方式，并且 HystrixCommand.run() 或者 HystrixObservableCommand.construct() 的执行时间超过了 timeout 时长的话，那么 command 所在的线程会抛出一个 TimeoutException，这时会执行 fallback 降级机制，不会去管 run() 或 construct() 返回的值了。另一种情况，如果 command 执行出错抛出了其它异常，那么也会走 fallback 降级。这两种情况下，Hystrix 都会发送异常事件给断路器统计。&lt;/p&gt;
&lt;p&gt;注意，我们是不可能终止掉一个调用严重延迟的依赖服务的线程的，只能说给你抛出来一个 TimeoutException。&lt;/p&gt;
&lt;p&gt;如果没有 timeout，也正常执行的话，那么调用线程就会拿到一些调用依赖服务获取到的结果，然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。&lt;/p&gt;
&lt;h4 id=&quot;步骤七：断路健康检查&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%B8%83%EF%BC%9A%E6%96%AD%E8%B7%AF%E5%81%A5%E5%BA%B7%E6%A3%80%E6%9F%A5&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤七：断路健康检查&lt;/h4&gt;
&lt;p&gt;Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计，根据异常事件发生的比例来决定是否要进行断路（熔断）。如果打开了断路器，那么在接下来一段时间内，会直接断路，返回降级结果。&lt;/p&gt;
&lt;p&gt;如果在之后，断路器尝试执行 command，调用没有出错，返回了正常结果，那么 Hystrix 就会把断路器关闭。&lt;/p&gt;
&lt;h4 id=&quot;步骤八：调用-fallback-降级机制&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E5%85%AB%EF%BC%9A%E8%B0%83%E7%94%A8-fallback-%E9%99%8D%E7%BA%A7%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤八：调用 fallback 降级机制&lt;/h4&gt;
&lt;p&gt;在以下几种情况中，Hystrix 会调用 fallback 降级机制。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;断路器处于打开状态；&lt;/li&gt;
&lt;li&gt;线程池 / 队列 / semaphore 满了；&lt;/li&gt;
&lt;li&gt;command 执行超时；&lt;/li&gt;
&lt;li&gt;run() 或者 construct() 抛出异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般在降级机制中，都建议给出一些默认的返回值，比如静态的一些代码逻辑，或者从内存中的缓存中提取一些数据，在这里尽量不要再进行网络请求了。&lt;/p&gt;
&lt;p&gt;在降级中，如果一定要进行网络调用的话，也应该将那个调用放在一个 HystrixCommand 中进行隔离。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HystrixCommand 中，实现 getFallback() 方法，可以提供降级机制。&lt;/li&gt;
&lt;li&gt;HystrixObservableCommand 中，实现 resumeWithFallback() 方法，返回一个 Observable 对象，可以提供降级结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果没有实现 fallback，或者 fallback 抛出了异常，Hystrix 会返回一个 Observable，但是不会返回任何数据。&lt;/p&gt;
&lt;p&gt;不同的 command 执行方式，其 fallback 为空或者异常时的返回结果不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于 execute()，直接抛出异常。&lt;/li&gt;
&lt;li&gt;对于 queue()，返回一个 Future，调用 get() 时抛出异常。&lt;/li&gt;
&lt;li&gt;对于 observe()，返回一个 Observable 对象，但是调用 subscribe() 方法订阅它时，立即抛出调用者的 onError() 方法。&lt;/li&gt;
&lt;li&gt;对于 toObservable()，返回一个 Observable 对象，但是调用 subscribe() 方法订阅它时，立即抛出调用者的 onError() 方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;不同的执行方式&quot;&gt;&lt;a href=&quot;#%E4%B8%8D%E5%90%8C%E7%9A%84%E6%89%A7%E8%A1%8C%E6%96%B9%E5%BC%8F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;不同的执行方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;execute()，获取一个 Future.get()，然后拿到单个结果。&lt;/li&gt;
&lt;li&gt;queue()，返回一个 Future。&lt;/li&gt;
&lt;li&gt;observe()，立即订阅 Observable，然后启动 8 大执行步骤，返回一个拷贝的 Observable，订阅时立即回调给你结果。&lt;/li&gt;
&lt;li&gt;toObservable()，返回一个原始的 Observable，必须手动订阅才会去执行 8 大步骤。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;基于-request-cache-请求缓存技术优化批量商品数据查询接口&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-request-cache-%E8%AF%B7%E6%B1%82%E7%BC%93%E5%AD%98%E6%8A%80%E6%9C%AF%E4%BC%98%E5%8C%96%E6%89%B9%E9%87%8F%E5%95%86%E5%93%81%E6%95%B0%E6%8D%AE%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 request cache 请求缓存技术优化批量商品数据查询接口&lt;/h3&gt;
&lt;p&gt;Hystrix command 执行时 8 大步骤第三步，就是检查 Request cache 是否有缓存。&lt;/p&gt;
&lt;p&gt;首先，有一个概念，叫做 Request Context 请求上下文，一般来说，在一个 web 应用中，如果我们用到了 Hystrix，我们会在一个 filter 里面，对每一个请求都施加一个请求上下文。就是说，每一次请求，就是一次请求上下文。然后在这次请求上下文中，我们会去执行 N 多代码，调用 N 多依赖服务，有的依赖服务可能还会调用好几次。&lt;/p&gt;
&lt;p&gt;在一次请求上下文中，如果有多个 command，参数都是一样的，调用的接口也是一样的，而结果可以认为也是一样的。那么这个时候，我们可以让第一个 command 执行返回的结果缓存在内存中，然后这个请求上下文后续的其它对这个依赖的调用全部从内存中取出缓存结果就可以了。&lt;/p&gt;
&lt;p&gt;这样的话，好处在于不用在一次请求上下文中反复多次执行一样的 command，避免重复执行网络请求，提升整个请求的性能。&lt;/p&gt;
&lt;p&gt;举个栗子。比如说我们在一次请求上下文中，请求获取 productId 为 1 的数据，第一次缓存中没有，那么会从商品服务中获取数据，返回最新数据结果，同时将数据缓存在内存中。后续同一次请求上下文中，如果还有获取 productId 为 1 的数据的请求，直接从缓存中取就好了。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-27-10-07-36-239f345702116484b6b7320ac165a986-aa752.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 339px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 76.99115044247787%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsSAAALEgHS3X78AAAB00lEQVQoz4WS147CQAxF5/+/BiR4AyFE74QSqkChEwiQ0FvgBK9WLDysH6wZj+/1tcdqPB4/Hg98Pp/v9/uz2Ww6nV4uFw7EXdfFc91ut4FAoN1u7/d727aHw+H9flcClqRfI3uxWNxuNygsy1oul7lczufzwavreq/XIwKdGo1GnBqNRrVaxVcqFVjn83mn0wG/Xq8nkwlP1AyHw4CJkE+QAgoa0zQJwUJotVodDodYLAYLwVqt1mw2CZIWDAaj0ShKYUc5Aj3ZwOr1OnlUwFMftZlMJp1Og6RmPB5vtVpQAyiXy9SXThVDAsxbqVSiH1io4/f7ISIDpKZp3W5X+spms+RLw47jqMFgQIfcqQYLSCowJHQWCgV0cd7tdqQahgFys9mAJAKvogGyi8UiIimOhyISiYRCofP5zMykPdLAXK9X2JmRfIQnm4zj8ShfcjqdGBgKkUOcVJ6YPKPiSSbPLPltEhR3lKdSqWQyyZASiQTPqOWApwh4KkMtywMXLIiC0fsqKWK8DDGwynjBsEayNgDIEXUYB0+2DMl8mSwMUdkE+Y+P5Xu3n/WkgiSJR9X7bou5X+ZV5oHpgRdPlMbo5XvnPyvTM1ul/zVWhY/5F/wEmDk0yToSgMwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 27 10 07 36&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-27-10-07-36-239f345702116484b6b7320ac165a986-aa752.png&quot; data-srcset=&quot;/static/2023-09-27-10-07-36-239f345702116484b6b7320ac165a986-bb04d.png 200w,
/static/2023-09-27-10-07-36-239f345702116484b6b7320ac165a986-aa752.png 339w&quot; data-sizes=&quot;(max-width: 339px) 100vw, 339px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key，然后 Hystrix 会自动进行缓存，接着在同一个 request context 内，再次访问的话，就会直接取用缓存。&lt;/p&gt;
&lt;p&gt;下面，我们结合一个具体的业务场景，来看一下如何使用 request cache 请求缓存技术。当然，以下代码只作为一个基本的 Demo 而已。&lt;/p&gt;
&lt;p&gt;现在，假设我们要做一个批量查询商品数据的接口，在这个里面，我们是用 HystrixCommand 一次性批量查询多个商品 id 的数据。但是这里有个问题，如果说 Nginx 在本地缓存失效了，重新获取一批缓存，传递过来的 productIds 都没有进行去重，比如 &lt;code class=&quot;language-text&quot;&gt;productIds=1,1,1,2,2&lt;/code&gt;，那么可能说，商品 id 出现了重复，如果按照我们之前的业务逻辑，可能就会重复对 &lt;code class=&quot;language-text&quot;&gt;productId=1&lt;/code&gt; 的商品查询三次，&lt;code class=&quot;language-text&quot;&gt;productId=2&lt;/code&gt; 的商品查询两次。&lt;/p&gt;
&lt;p&gt;我们对批量查询商品数据的接口，可以用 request cache 做一个优化，就是说一次请求，就是一次 request context，对相同的商品查询只执行一次，其余重复的都走 request cache。&lt;/p&gt;
&lt;h4 id=&quot;实现-hystrix-请求上下文过滤器并注册&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E7%8E%B0-hystrix-%E8%AF%B7%E6%B1%82%E4%B8%8A%E4%B8%8B%E6%96%87%E8%BF%87%E6%BB%A4%E5%99%A8%E5%B9%B6%E6%B3%A8%E5%86%8C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实现 Hystrix 请求上下文过滤器并注册&lt;/h4&gt;
&lt;p&gt;定义 HystrixRequestContextFilter 类，实现 Filter 接口。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72707247004174330000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`/**
 * Hystrix 请求上下文过滤器
 */
public class HystrixRequestContextFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (IOException | ServletException e) {
            e.printStackTrace();
        } finally {
            context.shutdown();
        }
    }

    @Override
    public void destroy() {

    }
}`, `72707247004174330000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Hystrix 请求上下文过滤器
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixRequestContextFilter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FilterConfig&lt;/span&gt; filterConfig&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ServletRequest&lt;/span&gt; servletRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletResponse&lt;/span&gt; servletResponse&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterChain&lt;/span&gt; filterChain&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HystrixRequestContext&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixRequestContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initializeContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            filterChain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;servletRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; servletResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ServletException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shutdown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后将该 filter 对象注册到 SpringBoot Application 中。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;28359671481351057000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SpringBootApplication
public class EshopApplication {

    public static void main(String[] args) {
        SpringApplication.run(EshopApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new HystrixRequestContextFilter());
        filterRegistrationBean.addUrlPatterns(&amp;quot;/*&amp;quot;);
        return filterRegistrationBean;
    }
}`, `28359671481351057000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EshopApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;EshopApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterRegistrationBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterRegistrationBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;FilterRegistrationBean&lt;/span&gt; filterRegistrationBean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FilterRegistrationBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixRequestContextFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        filterRegistrationBean&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addUrlPatterns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; filterRegistrationBean&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;command-重写-getcachekey-方法&quot;&gt;&lt;a href=&quot;#command-%E9%87%8D%E5%86%99-getcachekey-%E6%96%B9%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;command 重写 getCacheKey() 方法&lt;/h4&gt;
&lt;p&gt;在 GetProductInfoCommand 中，重写 getCacheKey() 方法，这样的话，每一次请求的结果，都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求，直接取缓存，无须再调用 run() 方法。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26367505960892860000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfoCommand extends HystrixCommand&lt;ProductInfo&gt; {

    private Long productId;

    private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey(&amp;quot;GetProductInfoCommand&amp;quot;);

    public GetProductInfoCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ProductInfoService&amp;quot;))
                .andCommandKey(KEY));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() {
        String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
        String response = HttpClientUtils.sendGetRequest(url);
        System.out.println(&amp;quot;调用接口查询商品数据，productId=&amp;quot; + productId);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    /**
     * 每次请求的结果，都会放在 Hystrix 绑定的请求上下文上
     *
     * @return cacheKey 缓存 key
     */
    @Override
    public String getCacheKey() {
        return &amp;quot;product_info_&amp;quot; + productId;
    }

    /**
     * 将某个商品 id 的缓存清空
     *
     * @param productId 商品 id
     */
    public static void flushCache(Long productId) {
        HystrixRequestCache.getInstance(KEY,
                HystrixConcurrencyStrategyDefault.getInstance()).clear(&amp;quot;product_info_&amp;quot; + productId);
    }
}`, `26367505960892860000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt; KEY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoCommand&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ProductInfoService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;调用接口查询商品数据，productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 每次请求的结果，都会放在 Hystrix 绑定的请求上下文上
     *
     * @return cacheKey 缓存 key
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCacheKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;product_info_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 将某个商品 id 的缓存清空
     *
     * @param productId 商品 id
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;flushCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HystrixRequestCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;HystrixConcurrencyStrategyDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;product_info_&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里写了一个 flushCache() 方法，用于我们开发手动删除缓存。&lt;/p&gt;
&lt;h4 id=&quot;controller-调用-command-查询商品信息&quot;&gt;&lt;a href=&quot;#controller-%E8%B0%83%E7%94%A8-command-%E6%9F%A5%E8%AF%A2%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;controller 调用 command 查询商品信息&lt;/h4&gt;
&lt;p&gt;在一次 web 请求上下文中，传入商品 id 列表，查询多条商品数据信息。对于每个 productId，都创建一个 command。&lt;/p&gt;
&lt;p&gt;如果 id 列表没有去重，那么重复的 id，第二次查询的时候就会直接走缓存。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;89065916714346050000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Controller
public class CacheController {

    /**
     * 一次性批量查询多条商品数据的请求
     *
     * @param productIds 以 , 分隔的商品 id 列表
     * @return 响应状态
     */
    @RequestMapping(&amp;quot;/getProductInfos&amp;quot;)
    @ResponseBody
    public String getProductInfos(String productIds) {
        for (String productId : productIds.split(&amp;quot;,&amp;quot;)) {
            // 对每个 productId，都创建一个 command
            GetProductInfoCommand getProductInfoCommand = new GetProductInfoCommand(Long.valueOf(productId));
            ProductInfo productInfo = getProductInfoCommand.execute();
            System.out.println(&amp;quot;是否是从缓存中取的结果：&amp;quot; + getProductInfoCommand.isResponseFromCache());
        }

        return &amp;quot;success&amp;quot;;
    }
}`, `89065916714346050000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 一次性批量查询多条商品数据的请求
     *
     * @param productIds 以 , 分隔的商品 id 列表
     * @return 响应状态
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/getProductInfos&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseBody&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProductInfos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; productId &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 对每个 productId，都创建一个 command&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; getProductInfoCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;是否是从缓存中取的结果：&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isResponseFromCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;发起请求&quot;&gt;&lt;a href=&quot;#%E5%8F%91%E8%B5%B7%E8%AF%B7%E6%B1%82&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;发起请求&lt;/h4&gt;
&lt;p&gt;调用接口，查询多个商品的信息。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;72094084908377500000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5`, `72094084908377500000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在控制台，我们可以看到以下结果。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;88148295074177780000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`调用接口查询商品数据，productId=1
是否是从缓存中取的结果：false
是否是从缓存中取的结果：true
是否是从缓存中取的结果：true
调用接口查询商品数据，productId=2
是否是从缓存中取的结果：false
是否是从缓存中取的结果：true
调用接口查询商品数据，productId=5
是否是从缓存中取的结果：false`, `88148295074177780000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;调用接口查询商品数据，productId=1
是否是从缓存中取的结果：false
是否是从缓存中取的结果：true
是否是从缓存中取的结果：true
调用接口查询商品数据，productId=2
是否是从缓存中取的结果：false
是否是从缓存中取的结果：true
调用接口查询商品数据，productId=5
是否是从缓存中取的结果：false&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;第一次查询 productId=1 的数据，会调用接口进行查询，不是从缓存中取结果。而随后再出现查询 productId=1 的请求，就直接取缓存了，这样的话，效率明显高很多。&lt;/p&gt;
&lt;h4 id=&quot;删除缓存&quot;&gt;&lt;a href=&quot;#%E5%88%A0%E9%99%A4%E7%BC%93%E5%AD%98&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;删除缓存&lt;/h4&gt;
&lt;p&gt;我们写一个 UpdateProductInfoCommand，在更新商品信息之后，手动调用之前写的 flushCache()，手动将缓存删除。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;53817172258059910000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class UpdateProductInfoCommand extends HystrixCommand&lt;Boolean&gt; {

    private Long productId;

    public UpdateProductInfoCommand(Long productId) {
        super(HystrixCommandGroupKey.Factory.asKey(&amp;quot;UpdateProductInfoGroup&amp;quot;));
        this.productId = productId;
    }

    @Override
    protected Boolean run() throws Exception {
        // 这里执行一次商品信息的更新
        // ...

        // 然后清空缓存
        GetProductInfoCommand.flushCache(productId);
        return true;
    }
}`, `53817172258059910000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UpdateProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UpdateProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;UpdateProductInfoGroup&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 这里执行一次商品信息的更新&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 然后清空缓存&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flushCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样，以后查询该商品的请求，第一次就会走接口调用去查询最新的商品信息。&lt;/p&gt;
&lt;h3 id=&quot;基于本地缓存的-fallback-降级机制&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98%E7%9A%84-fallback-%E9%99%8D%E7%BA%A7%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于本地缓存的 fallback 降级机制&lt;/h3&gt;
&lt;p&gt;Hystrix 出现以下四种情况，都会去调用 fallback 降级机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;断路器处于打开的状态。&lt;/li&gt;
&lt;li&gt;资源池已满（线程池 + 队列 / 信号量）。&lt;/li&gt;
&lt;li&gt;Hystrix 调用各种接口，或者访问外部依赖，比如 MySQL、Redis、Zookeeper、Kafka 等等，出现了任何异常的情况。&lt;/li&gt;
&lt;li&gt;访问外部依赖的时候，访问时间过长，报了 TimeoutException 异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;两种最经典的降级机制&quot;&gt;&lt;a href=&quot;#%E4%B8%A4%E7%A7%8D%E6%9C%80%E7%BB%8F%E5%85%B8%E7%9A%84%E9%99%8D%E7%BA%A7%E6%9C%BA%E5%88%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;两种最经典的降级机制&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;纯内存数据&lt;/p&gt;
&lt;p&gt;在降级逻辑中，你可以在内存中维护一个 ehcache，作为一个纯内存的基于 LRU 自动清理的缓存，让数据放在缓存内。如果说外部依赖有异常，fallback 这里直接尝试从 ehcache 中获取数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;默认值&lt;/p&gt;
&lt;p&gt;fallback 降级逻辑中，也可以直接返回一个默认值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 HystrixCommand，降级逻辑的书写，是通过实现 getFallback() 接口；而在 HystrixObservableCommand 中，则是实现 resumeWithFallback() 方法。&lt;/p&gt;
&lt;p&gt;现在，我们用一个简单的栗子，来演示 fallback 降级是怎么做的。&lt;/p&gt;
&lt;p&gt;比如，有这么个场景。我们现在有个包含 brandId 的商品数据，假设正常的逻辑是这样：拿到一个商品数据，根据 brandId 去调用品牌服务的接口，获取品牌的最新名称 brandName。&lt;/p&gt;
&lt;p&gt;假如说，品牌服务接口挂掉了，那么我们可以尝试从本地内存中，获取一份稍过期的数据，先凑合着用。&lt;/p&gt;
&lt;h4 id=&quot;步骤一：本地缓存获取数据&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%B8%80%EF%BC%9A%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤一：本地缓存获取数据&lt;/h4&gt;
&lt;p&gt;本地获取品牌名称的代码大致如下&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;9239464129509579000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`/**
 * 品牌名称本地缓存
 *
 */
public class BrandCache {

    private static Map&lt;Long, String&gt; brandMap = new HashMap&lt;&gt;();

    static {
        brandMap.put(1L, &amp;quot;Nike&amp;quot;);
    }

    /**
     * brandId 获取 brandName
     *
     * @param brandId 品牌 id
     * @return 品牌名
     */
    public static String getBrandName(Long brandId) {
        return brandMap.get(brandId);
    }`, `9239464129509579000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * 品牌名称本地缓存
 *
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BrandCache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; brandMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        brandMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Nike&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * brandId 获取 brandName
     *
     * @param brandId 品牌 id
     * @return 品牌名
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getBrandName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; brandId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; brandMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;步骤二：实现-getbrandnamecommand&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%BA%8C%EF%BC%9A%E5%AE%9E%E7%8E%B0-getbrandnamecommand&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤二：实现 GetBrandNameCommand&lt;/h4&gt;
&lt;p&gt;在 GetBrandNameCommand 中，run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称，如果调用失败，报错了，那么就会去调用 fallback 降级机制。&lt;/p&gt;
&lt;p&gt;这里，我们直接模拟接口调用报错，给它抛出个异常。&lt;/p&gt;
&lt;p&gt;而在 getFallback() 方法中，就是我们的降级逻辑，我们直接从本地的缓存中，获取到品牌名称的数据。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;49393355053876080000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`/**
 * 获取品牌名称的 command
 *
 */
public class GetBrandNameCommand extends HystrixCommand&lt;String&gt; {

    private Long brandId;

    public GetBrandNameCommand(Long brandId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;BrandService&amp;quot;))
                .andCommandKey(HystrixCommandKey.Factory.asKey(&amp;quot;GetBrandNameCommand&amp;quot;))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        // 设置降级机制最大并发请求数
                        .withFallbackIsolationSemaphoreMaxConcurrentRequests(15)));
        this.brandId = brandId;
    }

    @Override
    protected String run() throws Exception {
        // 这里正常的逻辑应该是去调用一个品牌服务的接口获取名称
        // 如果调用失败，报错了，那么就会去调用 fallback 降级机制

        // 这里我们直接模拟调用报错，抛出异常
        throw new Exception();
    }

    @Override
    protected String getFallback() {
        return BrandCache.getBrandName(brandId);
    }
}`, `49393355053876080000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * 获取品牌名称的 command
 *
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetBrandNameCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; brandId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetBrandNameCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; brandId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;BrandService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetBrandNameCommand&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置降级机制最大并发请求数&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withFallbackIsolationSemaphoreMaxConcurrentRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;brandId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; brandId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 这里正常的逻辑应该是去调用一个品牌服务的接口获取名称&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 如果调用失败，报错了，那么就会去调用 fallback 降级机制&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 这里我们直接模拟调用报错，抛出异常&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BrandCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBrandName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;FallbackIsolationSemaphoreMaxConcurrentRequests 用于设置 fallback 最大允许的并发请求量，默认值是 10，是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值，那么直接 reject。&lt;/p&gt;
&lt;h4 id=&quot;步骤三：cachecontroller-调用接口&quot;&gt;&lt;a href=&quot;#%E6%AD%A5%E9%AA%A4%E4%B8%89%EF%BC%9Acachecontroller-%E8%B0%83%E7%94%A8%E6%8E%A5%E5%8F%A3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;步骤三：CacheController 调用接口&lt;/h4&gt;
&lt;p&gt;在 CacheController 中，我们通过 productInfo 获取 brandId，然后创建 GetBrandNameCommand 并执行，去尝试获取 brandName。这里执行会报错，因为我们在 run() 方法中直接抛出异常，Hystrix 就会去调用 getFallback() 方法走降级逻辑。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78930600348799480000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@Controller
public class CacheController {

    @RequestMapping(&amp;quot;/getProductInfo&amp;quot;)
    @ResponseBody
    public String getProductInfo(Long productId) {
        HystrixCommand&lt;ProductInfo&gt; getProductInfoCommand = new GetProductInfoCommand(productId);

        ProductInfo productInfo = getProductInfoCommand.execute();
        Long brandId = productInfo.getBrandId();

        HystrixCommand&lt;String&gt; getBrandNameCommand = new GetBrandNameCommand(brandId);

        // 执行会抛异常报错，然后走降级
        String brandName = getBrandNameCommand.execute();
        productInfo.setBrandName(brandName);

        System.out.println(productInfo);
        return &amp;quot;success&amp;quot;;
    }
}`, `78930600348799480000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Controller&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheController&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/getProductInfo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ResponseBody&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; getProductInfoCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getProductInfoCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; brandId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBrandId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; getBrandNameCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetBrandNameCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 执行会抛异常报错，然后走降级&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; brandName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getBrandNameCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBrandName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;brandName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;success&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关于降级逻辑的演示，基本上就结束了。&lt;/p&gt;
&lt;h3 id=&quot;深入-hystrix-断路器执行原理&quot;&gt;&lt;a href=&quot;#%E6%B7%B1%E5%85%A5-hystrix-%E6%96%AD%E8%B7%AF%E5%99%A8%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;深入 Hystrix 断路器执行原理&lt;/h3&gt;
&lt;h4 id=&quot;状态机&quot;&gt;&lt;a href=&quot;#%E7%8A%B6%E6%80%81%E6%9C%BA&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;状态机&lt;/h4&gt;
&lt;p&gt;Hystrix 断路器有三种状态，分别是关闭（Closed）、打开（Open）与半开（Half-Open），三种状态转化关系如下：&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-b5415.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 800px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 48.33729216152019%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABV0lEQVQoz5WQW0/CQBCF9///IcOTD4ZIooDlEqxQWnqBUspuL3vt7rYO1BjFhMQv8zRzzmTOoO4PWpswDKMoopTGcRwEweGQ+r5fVRVM27b9VqLyi4IQIgS31hpjCoZ3x+1i7QSpVzLSS8EGQxCIKxdzlmWwe7cLfH+LMVZKcca8vfu2eXkaPzrb8TpeccGkFEIJqSRUGIXZMYNdCBY0Sv08BigoTvH+CEX2JStucqlG9acgMFHOb8x3aLuWS9GrUZ3Eue/VSSQINpfAGlJJKfP8lJ9zTIiGB15ptG7E2cpM89TKkxEndBgN54OH/HV0Xs4hrWBMMkarah/HhyTJ0pRTemlyXhaVyBckGm5mgyJ+NmSC8oWDV8ujMym8Tav1pZqms5bTOvS3YOusufab1lhDI1W4grhN+aGrNeqfXtc1XPs7Wwcp7n8CzWbOdDp13XfP87p/8gmJ4TvxB09hkwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 28 11 55 44&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-fee1c.png&quot; data-srcset=&quot;/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-a67b7.png 200w,
/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-0b187.png 400w,
/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-fee1c.png 800w,
/static/2023-09-28-11-55-44-b09482ba9e52a70a141f372366c8acf9-b5415.png 842w&quot; data-sizes=&quot;(max-width: 800px) 100vw, 800px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Closed 断路器关闭：调用下游的请求正常通过&lt;/li&gt;
&lt;li&gt;Open 断路器打开：阻断对下游服务的调用，直接走 Fallback 逻辑&lt;/li&gt;
&lt;li&gt;Half-Open 断路器处于半开状态：SleepWindowInMilliseconds&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;enabled&quot;&gt;&lt;a href=&quot;#enabled&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Enabled&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;44936822580815620000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerEnabled(boolean)`, `44936822580815620000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;控制断路器是否工作，包括跟踪依赖服务调用的健康状况，以及对异常情况过多时是否允许触发断路。默认值 true。&lt;/p&gt;
&lt;h5 id=&quot;circuitbreakerrequestvolumethreshold&quot;&gt;&lt;a href=&quot;#circuitbreakerrequestvolumethreshold&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;circuitBreaker.requestVolumeThreshold&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;32140906509389013000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerRequestVolumeThreshold(int)`, `32140906509389013000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerRequestVolumeThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;表示在一次统计的时间滑动窗口中（这个参数也很重要，下面有说到），至少经过多少个请求，才可能触发断路，默认值 20。经过 Hystrix 断路器的流量只有在超过了一定阈值后，才有可能触发断路。比如说，要求在 10s 内经过断路器的流量必须达到 20 个，而实际经过断路器的请求有 19 个，即使这 19 个请求全都失败，也不会去判断要不要断路。&lt;/p&gt;
&lt;h5 id=&quot;circuitbreakererrorthresholdpercentage&quot;&gt;&lt;a href=&quot;#circuitbreakererrorthresholdpercentage&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;circuitBreaker.errorThresholdPercentage&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;93571449089256010000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerErrorThresholdPercentage(int)`, `93571449089256010000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerErrorThresholdPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;表示异常比例达到多少，才会触发断路，默认值是 50(%)。&lt;/p&gt;
&lt;h5 id=&quot;circuitbreakersleepwindowinmilliseconds&quot;&gt;&lt;a href=&quot;#circuitbreakersleepwindowinmilliseconds&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;circuitBreaker.sleepWindowInMilliseconds&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62665950942894420000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerSleepWindowInMilliseconds(int)`, `62665950942894420000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerSleepWindowInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;断路器状态由 Close 转换到 Open，在之后 SleepWindowInMilliseconds 时间内，所有经过该断路器的请求会被断路，不调用后端服务，直接走 Fallback 降级机制，默认值 5000(ms)。&lt;/p&gt;
&lt;p&gt;而在该参数时间过后，断路器会变为 Half-Open 半开闭状态，尝试让一条请求经过断路器，看能不能正常调用。如果调用成功了，那么就自动恢复，断路器转为 Close 状态。&lt;/p&gt;
&lt;h5 id=&quot;forceopen&quot;&gt;&lt;a href=&quot;#forceopen&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ForceOpen&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;57829115999851900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerForceOpen(boolean)`, `57829115999851900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerForceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果设置为 true 的话，直接强迫打开断路器，相当于是手动断路了，手动降级，默认值是 false。&lt;/p&gt;
&lt;h5 id=&quot;forceclosed&quot;&gt;&lt;a href=&quot;#forceclosed&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ForceClosed&lt;/h5&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78263101904969760000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withCircuitBreakerForceClosed(boolean)`, `78263101904969760000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerForceClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果设置为 true，直接强迫关闭断路器，相当于手动停止断路了，手动升级，默认值是 false。&lt;/p&gt;
&lt;h4 id=&quot;metrics-统计器&quot;&gt;&lt;a href=&quot;#metrics-%E7%BB%9F%E8%AE%A1%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Metrics 统计器&lt;/h4&gt;
&lt;p&gt;与 Hystrix 断路器紧密协作的，还有另一个重要组件 —— 统计器（Metrics）。统计器中最重要的参数要数滑动窗口（metrics.rollingStats.timeInMilliseconds）以及桶（metrics.rollingStats.numBuckets）了，这里引用一段博文来解释滑动窗口（默认值是 10000 ms）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一位乘客坐在正在行驶的列车的靠窗座位上，列车行驶的公路两侧种着一排挺拔的白杨树，随着列车的前进，路边的白杨树迅速从窗口滑过。我们用每棵树来代表一个请求，用列车的行驶代表时间的流逝，那么，列车上的这个窗口就是一个典型的滑动窗口，这个乘客能通过窗口看到的白杨树就是 Hystrix 要统计的数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hystrix 并不是只要有一条请求经过就去统计，而是将整个滑动窗口均分为 numBuckets 份，时间每经过一份就去统计一次。在经过一个时间窗口后，才会判断断路器状态要不要开启，请看下面的例子。&lt;/p&gt;
&lt;h4 id=&quot;实例-demo&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E4%BE%8B-demo&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实例 Demo&lt;/h4&gt;
&lt;h5 id=&quot;hystrixcommand-配置参数&quot;&gt;&lt;a href=&quot;#hystrixcommand-%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;HystrixCommand 配置参数&lt;/h5&gt;
&lt;p&gt;在 GetProductInfoCommand 中配置 Setter 断路器相关参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;滑动窗口中，最少 20 个请求，才可能触发断路。&lt;/li&gt;
&lt;li&gt;异常比例达到 40% 时，才触发断路。&lt;/li&gt;
&lt;li&gt;断路后 3000ms 内，所有请求都被 reject，直接走 fallback 降级，不会调用 run() 方法。3000ms 过后，变为 half-open 状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;run() 方法中，我们判断一下 productId 是否为 -1，是的话，直接抛出异常。这么写，我们之后测试的时候就可以传入 productId = -1，模拟服务执行异常了。&lt;/p&gt;
&lt;p&gt;在降级逻辑中，我们直接给它返回降级商品就好了。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;62348193794403110000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfoCommand extends HystrixCommand&lt;ProductInfo&gt; {

    private Long productId;

    private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey(&amp;quot;GetProductInfoCommand&amp;quot;);

    public GetProductInfoCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ProductInfoService&amp;quot;))
                .andCommandKey(KEY)
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        // 是否允许断路器工作
                        .withCircuitBreakerEnabled(true)
                        // 滑动窗口中，最少有多少个请求，才可能触发断路
                        .withCircuitBreakerRequestVolumeThreshold(20)
                        // 异常比例达到多少，才触发断路，默认 50%
                        .withCircuitBreakerErrorThresholdPercentage(40)
                        // 断路后多少时间内直接 reject 请求，之后进入 half-open 状态，默认 5000ms
                        .withCircuitBreakerSleepWindowInMilliseconds(3000)));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() throws Exception {
        System.out.println(&amp;quot;调用接口查询商品数据，productId =&amp;quot; + productId);

        if (productId == -1L) {
            throw new Exception();
        }

        String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
        String response = HttpClientUtils.sendGetRequest(url);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    @Override
    protected ProductInfo getFallback() {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setName(&amp;quot;降级商品&amp;quot;);
        return productInfo;
    }
}`, `62348193794403110000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt; KEY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoCommand&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ProductInfoService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 是否允许断路器工作&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 滑动窗口中，最少有多少个请求，才可能触发断路&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerRequestVolumeThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 异常比例达到多少，才触发断路，默认 50%&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerErrorThresholdPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 断路后多少时间内直接 reject 请求，之后进入 half-open 状态，默认 5000ms&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerSleepWindowInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;调用接口查询商品数据，productId =&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;降级商品&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;断路测试类&quot;&gt;&lt;a href=&quot;#%E6%96%AD%E8%B7%AF%E6%B5%8B%E8%AF%95%E7%B1%BB&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;断路测试类&lt;/h5&gt;
&lt;p&gt;我们在测试类中，前 30 次请求，传入 productId = -1，然后休眠 3s，之后 70 次请求，传入 productId = 1。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;31530306704907660000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SpringBootTest
@RunWith(SpringRunner.class)
public class CircuitBreakerTest {

    @Test
    public void testCircuitBreaker() {
        String baseURL = &amp;quot;http://localhost:8080/getProductInfo?productId=&amp;quot;;

        for (int i = 0; i &lt; 30; ++i) {
            // 传入 -1，会抛出异常，然后走降级逻辑
            HttpClientUtils.sendGetRequest(baseURL + &amp;quot;-1&amp;quot;);
        }

        TimeUtils.sleep(3);
        System.out.println(&amp;quot;After sleeping...&amp;quot;);

        for (int i = 31; i &lt; 100; ++i) {
            // 传入 1，走服务正常调用
            HttpClientUtils.sendGetRequest(baseURL + &amp;quot;1&amp;quot;);
        }
    }
}`, `31530306704907660000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RunWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpringRunner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CircuitBreakerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testCircuitBreaker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; baseURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/getProductInfo?productId=&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 传入 -1，会抛出异常，然后走降级逻辑&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseURL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;TimeUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;After sleeping...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 传入 1，走服务正常调用&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseURL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;测试结果&quot;&gt;&lt;a href=&quot;#%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;测试结果&lt;/h4&gt;
&lt;p&gt;测试结果，我们可以明显看出系统断路与恢复的整个过程。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;58858005774424015000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`调用接口查询商品数据，productId = -1
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
// ...
// 这里重复打印了 20 次上面的结果


ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
// ...
// 这里重复打印了 8 次上面的结果


// 休眠 3s 后
调用接口查询商品数据，productId = 1
ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, specification=iphone7的规格, service=iphone7的售后服务, color=红色,白色,黑色, size=5.5, shopId=1, modifiedTime=2017-01-01 12:00:00, cityId=1, cityName=null, brandId=1, brandName=null)
// ...
// 这里重复打印了 69 次上面的结果`, `58858005774424015000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;调用接口查询商品数据，productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;降级商品&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; price&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pictureList&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; specification&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shopId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modifiedTime&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 这里重复打印了 20 次上面的结果&lt;/span&gt;


&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;降级商品&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; price&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pictureList&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; specification&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shopId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modifiedTime&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 这里重复打印了 8 次上面的结果&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;// 休眠 3s 后&lt;/span&gt;
调用接口查询商品数据，productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;iphone7手机&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; price&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5599.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pictureList&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; specification&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;iphone7的规格&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;iphone7的售后服务&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;红色&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;白色&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;黑色&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shopId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modifiedTime&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2017&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;00&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cityName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandId&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; brandName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 这里重复打印了 69 次上面的结果&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;前 30 次请求，我们传入的 productId 为 -1，所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用，每次都抛异常并且走降级，21 次过后，断路器就被打开了。&lt;/p&gt;
&lt;p&gt;之后的 9 次请求，都不会执行 run() 方法，也就不会打印以下信息。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;63656322584503040000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`调用接口查询商品数据，productId = -1`, `63656322584503040000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;调用接口查询商品数据，productId = -1&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而是直接走降级逻辑，调用 getFallback() 执行。&lt;/p&gt;
&lt;p&gt;休眠了 3s 后，我们在之后的 70 次请求中，都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 half-open 状态。因此 Hystrix 会尝试执行请求，发现成功了，那么断路器关闭，之后的所有请求也都能正常调用了。&lt;/p&gt;
&lt;h3 id=&quot;深入-hystrix-线程池隔离与接口限流&quot;&gt;&lt;a href=&quot;#%E6%B7%B1%E5%85%A5-hystrix-%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%9A%94%E7%A6%BB%E4%B8%8E%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;深入 Hystrix 线程池隔离与接口限流&lt;/h3&gt;
&lt;p&gt;前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断，这一讲，我们来详细说说 Hystrix 的线程池隔离与接口限流。&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2023-09-28-12-02-15-63fa307b76f648b60f5304df4340ab1f-4a060.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
  
  &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; ; max-width: 354px; margin-left: auto; margin-right: auto;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 172.5988700564972%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAjCAIAAAAblL1PAAAACXBIWXMAAAsSAAALEgHS3X78AAADVUlEQVRIx3XVV1PrQAwF4Pz/38MLMzCUoYd26Qk9lFCSECCUhJvc+7FijM0YPXjWax3p6Ei7rlxcXFxeXnre3d0tLi4uLCxcX1+fnJycnZ2dnp52Op2/yT6SjUajfzmrHB8fc63X60dHR/Pz82trawcHB6IsLS0BT09Pb21t+XR4eFir1W5vb2GyEJXIsLOzw0nO19fXh4cHeJuPj492/iTjsL29jWABLDPveHl+fgbo9/tcz8/P8ex2u+/v7/EV+Zubm3IwV4H39/c3NjaazWbU0mg0rAPA4VfwcDj0rZHMpjot0M54lWfGzert7e3l5QVtPFdWVqTF3ysVLH4FPz09QSrSuplMTq+e0UgLvICjBOsCWHg8V1dXvcqpYMXT35PyQgyT/Uo7GBKcMHqjVRa9Xs8+FnwMTPTv/v5egQXwKJmFEJIDDAaD0IJs8mOBUbVatTaOX0OCnsIExgpzyFarpTzdNliQYDY3NzfNCWcSRAsqMdWeKAjRbrd5mE2v1hiKfnV15QkM6amcr5p9yDTAU0ghjDHa8qswtI3ixRLx+2AEODsx8UTYIQlY7ECCqSvOxs/MTGYlqFYVwZYQQbKTDDKk+gZrgN5Q0jqOXkyoTWrJRi1EVGRdAMPIg5UKEdOJ2dlZIdwKhlT9YFNTU9qpt0gVwD/6jKQ5icwxM+vr6yhMTk66J0LFEnBeHt2SNjyE6Cbb29vDv6B2diRDMMRIFedBHiJFOK1SSwk4TpUxoge1ZmZmtApVC4JR2JmpJhO3hDbOds2AzKTa3d01TDFYy8vLhhkRtAXiU047pkWRaAOgqot0Mq0TExNjY2Nk053MuQDuJXPoKSy/dTakKMiMl50SMOZx+2e3h2fG003EjU9hPPNgUQlGHtxqydCOq0ctomTXWEmfXX1x11Er6MHEleBTOTgvGCelxsxmyNjX4fw1xj7B9Iib0YfosFZRm85RMxgJ9dx45+/ALzC2BsiHubk5dwVhzYwQkNZGNf5VVODPzQjowuepyg5GjAoKvqmZkxJoLi43UdRiQuPXZ6eCHm5mw4SGWqbVJDpP+Su61D4vQIHjFyWJYdAq9CgkIi0GyfrJvI5yVlGV2gwwpK6q2Z+dNkjadBN4dULGx8etHbI8nf8lqIVNRF7MmwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;
      &lt;img class=&quot;gatsby-resp-image-image lazy&quot; style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px #fafafa;&quot; alt=&quot;2023 09 28 12 02 15&quot; title=&quot;&quot; data-src=&quot;/static/2023-09-28-12-02-15-63fa307b76f648b60f5304df4340ab1f-4a060.png&quot; data-srcset=&quot;/static/2023-09-28-12-02-15-63fa307b76f648b60f5304df4340ab1f-ca453.png 200w,
/static/2023-09-28-12-02-15-63fa307b76f648b60f5304df4340ab1f-4a060.png 354w&quot; data-sizes=&quot;(max-width: 354px) 100vw, 354px&quot;&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/body&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Hystrix 通过判断线程池或者信号量是否已满，超出容量的请求，直接 Reject 走降级，从而达到限流的作用。&lt;/p&gt;
&lt;p&gt;限流是限制对后端服务的访问量，比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制，其实是为了避免过大的流量直接打死后端的服务。&lt;/p&gt;
&lt;h4 id=&quot;线程池隔离技术的设计&quot;&gt;&lt;a href=&quot;#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%9A%94%E7%A6%BB%E6%8A%80%E6%9C%AF%E7%9A%84%E8%AE%BE%E8%AE%A1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;线程池隔离技术的设计&lt;/h4&gt;
&lt;p&gt;Hystrix 采用了 Bulkhead Partition 舱壁隔离技术，来将外部依赖进行资源隔离，进而避免任何外部依赖的故障导致本服务崩溃。&lt;/p&gt;
&lt;p&gt;舱壁隔离，是说将船体内部空间区隔划分成若干个隔舱，一旦某几个隔舱发生破损进水，水流不会在其间相互流动，如此一来船舶在受损时，依然能具有足够的浮力和稳定性，进而减低立即沉船的危险。&lt;/p&gt;
&lt;p&gt;Hystrix 对每个外部依赖用一个单独的线程池，这样的话，如果对那个外部依赖调用延迟很严重，最多就是耗尽那个依赖自己的线程池而已，不会影响其他的依赖调用。&lt;/p&gt;
&lt;h4 id=&quot;hystrix-应用线程池机制的场景&quot;&gt;&lt;a href=&quot;#hystrix-%E5%BA%94%E7%94%A8%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%9C%BA%E5%88%B6%E7%9A%84%E5%9C%BA%E6%99%AF&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hystrix 应用线程池机制的场景&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;每个服务都会调用几十个后端依赖服务，那些后端依赖服务通常是由很多不同的团队开发的。&lt;/li&gt;
&lt;li&gt;每个后端依赖服务都会提供它自己的 client 调用库，比如说用 thrift 的话，就会提供对应的 thrift 依赖。&lt;/li&gt;
&lt;li&gt;client 调用库随时会变更。&lt;/li&gt;
&lt;li&gt;client 调用库随时可能会增加新的网络请求的逻辑。&lt;/li&gt;
&lt;li&gt;client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。&lt;/li&gt;
&lt;li&gt;client 调用库一般都对调用者来说是个黑盒，包括实现细节、网络访问、默认配置等等。&lt;/li&gt;
&lt;li&gt;在真实的生产环境中，经常会出现调用者，突然间惊讶的发现，client 调用库发生了某些变化。&lt;/li&gt;
&lt;li&gt;即使 client 调用库没有改变，依赖服务本身可能会发生逻辑上的变化。&lt;/li&gt;
&lt;li&gt;有些依赖的 client 调用库可能还会拉取其他的依赖库，而且可能那些依赖库配置的不正确。&lt;/li&gt;
&lt;li&gt;大多数网络请求都是同步调用的。&lt;/li&gt;
&lt;li&gt;调用失败和延迟，也有可能会发生在 client 调用库本身的代码中，不一定就是发生在网络请求中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说，就是你必须默认 client 调用库很不靠谱，而且随时可能发生各种变化，所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。&lt;/p&gt;
&lt;h4 id=&quot;线程池机制的优点&quot;&gt;&lt;a href=&quot;#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%9C%BA%E5%88%B6%E7%9A%84%E4%BC%98%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;线程池机制的优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;任何一个依赖服务都可以被隔离在自己的线程池内，即使自己的线程池资源填满了，也不会影响任何其他的服务调用。&lt;/li&gt;
&lt;li&gt;服务可以随时引入一个新的依赖服务，因为即使这个新的依赖服务有问题，也不会影响其他任何服务的调用。&lt;/li&gt;
&lt;li&gt;当一个故障的依赖服务重新变好的时候，可以通过清理掉线程池，瞬间恢复该服务的调用，而如果是 tomcat 线程池被占满，再恢复就很麻烦。&lt;/li&gt;
&lt;li&gt;如果一个 client 调用库配置有问题，线程池的健康状况随时会报告，比如成功 / 失败 / 拒绝 / 超时的次数统计，然后可以近实时热修改依赖服务的调用配置，而不用停机。&lt;/li&gt;
&lt;li&gt;基于线程池的异步本质，可以在同步的调用之上，构建一层异步调用层。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说，最大的好处，就是资源隔离，确保说任何一个依赖服务故障，不会拖垮当前的这个服务。&lt;/p&gt;
&lt;h4 id=&quot;线程池机制的缺点&quot;&gt;&lt;a href=&quot;#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%9C%BA%E5%88%B6%E7%9A%84%E7%BC%BA%E7%82%B9&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;线程池机制的缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;线程池机制最大的缺点就是增加了 CPU 的开销。除了 tomcat 本身的调用线程之外，还有 Hystrix 自己管理的线程池。&lt;/li&gt;
&lt;li&gt;每个 command 的执行都依托一个独立的线程，会进行排队，调度，还有上下文切换。&lt;/li&gt;
&lt;li&gt;Hystrix 官方自己做了一个多线程异步带来的额外开销统计，通过对比多线程异步调用 + 同步调用得出，Netflix API 每天通过 Hystrix 执行 10 亿次调用，每个服务实例有 40 个以上的线程池，每个线程池有 10 个左右的线程。最后发现说，用 Hystrix 的额外开销，就是给请求带来了 3ms 左右的延时，最多延时在 10ms 以内，相比于可用性和稳定性的提升，这是可以接受的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制，而不是通过线程池 / 队列的大小来限制流量。&lt;/p&gt;
&lt;p&gt;semaphore 技术可以用来限流和削峰，但是不能用来对调用延迟的服务进行 timeout 和隔离。&lt;/p&gt;
&lt;p&gt;execution.isolation.strategy 设置为 SEMAPHORE，那么 Hystrix 就会用 semaphore 机制来替代线程池机制，来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候，底层的网络调用延迟很严重，那么是无法 timeout 的，只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后，就会立即开启限流。&lt;/p&gt;
&lt;h4 id=&quot;接口限流-demo&quot;&gt;&lt;a href=&quot;#%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81-demo&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;接口限流 Demo&lt;/h4&gt;
&lt;p&gt;假设一个线程池大小为 8，等待队列的大小为 10。timeout 时长我们设置长一些，20s。&lt;/p&gt;
&lt;p&gt;在 command 内部，写死代码，做一个 sleep，比如 sleep 3s。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;withCoreSize：设置线程池大小。&lt;/li&gt;
&lt;li&gt;withMaxQueueSize：设置等待队列大小。&lt;/li&gt;
&lt;li&gt;withQueueSizeRejectionThreshold：这个与 withMaxQueueSize 配合使用，等待队列的大小，取得是这两个参数的较小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果只设置了线程池大小，另外两个 queue 相关参数没有设置的话，等待队列是处于关闭的状态。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;23523501291363914000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfoCommand extends HystrixCommand&lt;ProductInfo&gt; {

    private Long productId;

    private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey(&amp;quot;GetProductInfoCommand&amp;quot;);

    public GetProductInfoCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ProductInfoService&amp;quot;))
                .andCommandKey(KEY)
                // 线程池相关配置信息
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                        // 设置线程池大小为 8
                        .withCoreSize(8)
                        // 设置等待队列大小为 10
                        .withMaxQueueSize(10)
                        .withQueueSizeRejectionThreshold(12))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withCircuitBreakerEnabled(true)
                        .withCircuitBreakerRequestVolumeThreshold(20)
                        .withCircuitBreakerErrorThresholdPercentage(40)
                        .withCircuitBreakerSleepWindowInMilliseconds(3000)
                        // 设置超时时间
                        .withExecutionTimeoutInMilliseconds(20000)
                        // 设置 fallback 最大请求并发数
                        .withFallbackIsolationSemaphoreMaxConcurrentRequests(30)));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() throws Exception {
        System.out.println(&amp;quot;调用接口查询商品数据，productId =&amp;quot; + productId);

        if (productId == -1L) {
            throw new Exception();
        }

        // 请求过来，会在这里hang住3秒钟
        if (productId == -2L) {
            TimeUtils.sleep(3);
        }

        String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
        String response = HttpClientUtils.sendGetRequest(url);
        System.out.println(response);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    @Override
    protected ProductInfo getFallback() {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setName(&amp;quot;降级商品&amp;quot;);
        return productInfo;
    }
}`, `23523501291363914000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt; KEY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoCommand&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ProductInfoService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 线程池相关配置信息&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andThreadPoolPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixThreadPoolProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置线程池大小为 8&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCoreSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置等待队列大小为 10&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withMaxQueueSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withQueueSizeRejectionThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerRequestVolumeThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerErrorThresholdPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerSleepWindowInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置超时时间&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionTimeoutInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置 fallback 最大请求并发数&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withFallbackIsolationSemaphoreMaxConcurrentRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;调用接口查询商品数据，productId =&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 请求过来，会在这里hang住3秒钟&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;TimeUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;降级商品&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我们模拟 25 个请求。前 8 个请求，调用接口时会直接被 hang 住 3s，那么后面的 10 个请求会先进入等待队列中等待前面的请求执行完毕。最后的 7 个请求过来，会直接被 reject，调用 fallback 降级逻辑。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;23653219243655533000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SpringBootTest
@RunWith(SpringRunner.class)
public class RejectTest {

    @Test
    public void testReject() {
        for (int i = 0; i &lt; 25; ++i) {
            new Thread(() -&gt; HttpClientUtils.sendGetRequest(&amp;quot;http://localhost:8080/getProductInfo?productId=-2&amp;quot;)).start();
        }
        // 防止主线程提前结束执行
        TimeUtils.sleep(50);
    }
}`, `23653219243655533000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RunWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpringRunner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RejectTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testReject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/getProductInfo?productId=-2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 防止主线程提前结束执行&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TimeUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从执行结果中，我们可以明显看出一共打印出了 7 个降级商品。这也就是请求数超过线程池 + 队列的数量而直接被 reject 的结果。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;78203996574404260000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
{&amp;quot;id&amp;quot;: -2, &amp;quot;name&amp;quot;: &amp;quot;iphone7手机&amp;quot;, &amp;quot;price&amp;quot;: 5599, &amp;quot;pictureList&amp;quot;:&amp;quot;a.jpg,b.jpg&amp;quot;, &amp;quot;specification&amp;quot;: &amp;quot;iphone7的规格&amp;quot;, &amp;quot;service&amp;quot;: &amp;quot;iphone7的售后服务&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;红色,白色,黑色&amp;quot;, &amp;quot;size&amp;quot;: &amp;quot;5.5&amp;quot;, &amp;quot;shopId&amp;quot;: 1, &amp;quot;modifiedTime&amp;quot;: &amp;quot;2017-01-01 12:00:00&amp;quot;, &amp;quot;cityId&amp;quot;: 1, &amp;quot;brandId&amp;quot;: 1}
// 后面都是一些正常的商品信息，就不贴出来了
// ...`, `78203996574404260000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
调用接口查询商品数据，productId = -2
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
{&amp;quot;id&amp;quot;: -2, &amp;quot;name&amp;quot;: &amp;quot;iphone7手机&amp;quot;, &amp;quot;price&amp;quot;: 5599, &amp;quot;pictureList&amp;quot;:&amp;quot;a.jpg,b.jpg&amp;quot;, &amp;quot;specification&amp;quot;: &amp;quot;iphone7的规格&amp;quot;, &amp;quot;service&amp;quot;: &amp;quot;iphone7的售后服务&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;红色,白色,黑色&amp;quot;, &amp;quot;size&amp;quot;: &amp;quot;5.5&amp;quot;, &amp;quot;shopId&amp;quot;: 1, &amp;quot;modifiedTime&amp;quot;: &amp;quot;2017-01-01 12:00:00&amp;quot;, &amp;quot;cityId&amp;quot;: 1, &amp;quot;brandId&amp;quot;: 1}
// 后面都是一些正常的商品信息，就不贴出来了
// ...&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;基于-timeout-机制为服务接口调用超时提供安全保护&quot;&gt;&lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-timeout-%E6%9C%BA%E5%88%B6%E4%B8%BA%E6%9C%8D%E5%8A%A1%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E8%B6%85%E6%97%B6%E6%8F%90%E4%BE%9B%E5%AE%89%E5%85%A8%E4%BF%9D%E6%8A%A4&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;基于 timeout 机制为服务接口调用超时提供安全保护&lt;/h3&gt;
&lt;p&gt;一般来说，在调用依赖服务的接口的时候，比较常见的一个问题就是超时。超时是在一个复杂的分布式系统中，系统不稳定，或者系统抖动的现象。出现大量超时，线程资源会被 hang 死，从而导致吞吐量大幅度下降，甚至服务崩溃。&lt;/p&gt;
&lt;p&gt;你去调用各种各样的依赖服务，特别是在大公司，你甚至都不认识开发一个服务的人，你都不知道那个人的技术水平怎么样，对那个人根本不了解。&lt;/p&gt;
&lt;p&gt;Peter Steiner 说过，&lt;code class=&quot;language-text&quot;&gt;On the Internet, nobody knows you&amp;#39;re a dog&lt;/code&gt;，也就是说在互联网的另外一头，你都不知道甚至坐着一条狗。&lt;/p&gt;
&lt;p&gt;像特别复杂的分布式系统，特别是在大公司里，多个团队、大型协作，你可能都不知道服务是谁的，很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定，有时候 2ms，有时候 200ms，甚至 2s，都有可能。&lt;/p&gt;
&lt;p&gt;如果你不对各种依赖服务接口的调用做超时控制，来给你的服务提供安全保护措施，那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢，大量的线程被卡死。如果你做了资源的隔离，那么也就是线程池的线程被卡死，但其实我们可以做超时控制，没必要让它们全卡死。&lt;/p&gt;
&lt;h4 id=&quot;timeoutmilliseconds&quot;&gt;&lt;a href=&quot;#timeoutmilliseconds&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TimeoutMilliseconds&lt;/h4&gt;
&lt;p&gt;在 Hystrix 中，我们可以手动设置 timeout 时长，如果一个 command 运行时间超过了设定的时长，那么就被认为是 timeout，然后 Hystrix command 标识为 timeout，同时执行 fallback 降级逻辑。&lt;/p&gt;
&lt;p&gt;TimeoutMilliseconds 默认值是 1000，也就是 1000ms。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;15207986536263141000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    ..withExecutionTimeoutInMilliseconds(int)`, `15207986536263141000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionTimeoutInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;timeoutenabled&quot;&gt;&lt;a href=&quot;#timeoutenabled&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TimeoutEnabled&lt;/h4&gt;
&lt;p&gt;这个参数用于控制是否要打开 timeout 机制，默认值是 true。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;29132662036918424000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`HystrixCommandProperties.Setter()
    .withExecutionTimeoutEnabled(boolean)`, `29132662036918424000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionTimeoutEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;实例-demo-1&quot;&gt;&lt;a href=&quot;#%E5%AE%9E%E4%BE%8B-demo-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;实例 Demo&lt;/h4&gt;
&lt;p&gt;我们在 command 中，将超时时间设置为 500ms，然后在 run() 方法中，设置休眠时间 1s，这样一个请求过来，直接休眠 1s，结果就会因为超时而执行降级逻辑。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;40797355810208645000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class GetProductInfoCommand extends HystrixCommand&lt;ProductInfo&gt; {

    private Long productId;

    private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey(&amp;quot;GetProductInfoCommand&amp;quot;);

    public GetProductInfoCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(&amp;quot;ProductInfoService&amp;quot;))
                .andCommandKey(KEY)
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                        .withCoreSize(8)
                        .withMaxQueueSize(10)
                        .withQueueSizeRejectionThreshold(8))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withCircuitBreakerEnabled(true)
                        .withCircuitBreakerRequestVolumeThreshold(20)
                        .withCircuitBreakerErrorThresholdPercentage(40)
                        .withCircuitBreakerSleepWindowInMilliseconds(3000)
                        // 设置是否打开超时，默认是 true
                        .withExecutionTimeoutEnabled(true)
                        // 设置超时时间，默认 1000(ms)
                        .withExecutionTimeoutInMilliseconds(500)
                        .withFallbackIsolationSemaphoreMaxConcurrentRequests(30)));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() throws Exception {
        System.out.println(&amp;quot;调用接口查询商品数据，productId =&amp;quot; + productId);

        // 休眠1s
        TimeUtils.sleep(1);

        String url = &amp;quot;http://localhost:8081/getProductInfo?productId=&amp;quot; + productId;
        String response = HttpClientUtils.sendGetRequest(url);
        System.out.println(response);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    @Override
    protected ProductInfo getFallback() {
        ProductInfo productInfo = new ProductInfo();
        productInfo.setName(&amp;quot;降级商品&amp;quot;);
        return productInfo;
    }
}`, `40797355810208645000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommand&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt; KEY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HystrixCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GetProductInfoCommand&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetProductInfoCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandGroupKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ProductInfoService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KEY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andThreadPoolPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixThreadPoolProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCoreSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withMaxQueueSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withQueueSizeRejectionThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;andCommandPropertiesDefaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HystrixCommandProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Setter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerRequestVolumeThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerErrorThresholdPercentage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withCircuitBreakerSleepWindowInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置是否打开超时，默认是 true&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionTimeoutEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// 设置超时时间，默认 1000(ms)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withExecutionTimeoutInMilliseconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withFallbackIsolationSemaphoreMaxConcurrentRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;调用接口查询商品数据，productId =&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 休眠1s&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TimeUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8081/getProductInfo?productId=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSONObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt; productInfo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        productInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;降级商品&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; productInfo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在测试类中，我们直接发起请求。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;43931589625449830000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`@SpringBootTest
@RunWith(SpringRunner.class)
public class TimeoutTest {

    @Test
    public void testTimeout() {
        HttpClientUtils.sendGetRequest(&amp;quot;http://localhost:8080/getProductInfo?productId=1&amp;quot;);
    }
}`, `43931589625449830000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@RunWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpringRunner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeoutTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HttpClientUtils&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendGetRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/getProductInfo?productId=1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结果中可以看到，打印出了降级商品相关信息。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;656771913098430600&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
{&amp;quot;id&amp;quot;: 1, &amp;quot;name&amp;quot;: &amp;quot;iphone7手机&amp;quot;, &amp;quot;price&amp;quot;: 5599, &amp;quot;pictureList&amp;quot;:&amp;quot;a.jpg,b.jpg&amp;quot;, &amp;quot;specification&amp;quot;: &amp;quot;iphone7的规格&amp;quot;, &amp;quot;service&amp;quot;: &amp;quot;iphone7的售后服务&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;红色,白色,黑色&amp;quot;, &amp;quot;size&amp;quot;: &amp;quot;5.5&amp;quot;, &amp;quot;shopId&amp;quot;: 1, &amp;quot;modifiedTime&amp;quot;: &amp;quot;2017-01-01 12:00:00&amp;quot;, &amp;quot;cityId&amp;quot;: 1, &amp;quot;brandId&amp;quot;: 1}`, `656771913098430600`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;&quot;
              &gt;
                
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-text line-numbers&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
{&amp;quot;id&amp;quot;: 1, &amp;quot;name&amp;quot;: &amp;quot;iphone7手机&amp;quot;, &amp;quot;price&amp;quot;: 5599, &amp;quot;pictureList&amp;quot;:&amp;quot;a.jpg,b.jpg&amp;quot;, &amp;quot;specification&amp;quot;: &amp;quot;iphone7的规格&amp;quot;, &amp;quot;service&amp;quot;: &amp;quot;iphone7的售后服务&amp;quot;, &amp;quot;color&amp;quot;: &amp;quot;红色,白色,黑色&amp;quot;, &amp;quot;size&amp;quot;: &amp;quot;5.5&amp;quot;, &amp;quot;shopId&amp;quot;: 1, &amp;quot;modifiedTime&amp;quot;: &amp;quot;2017-01-01 12:00:00&amp;quot;, &amp;quot;cityId&amp;quot;: 1, &amp;quot;brandId&amp;quot;: 1}&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;高可用系统&quot;&gt;&lt;a href=&quot;#%E9%AB%98%E5%8F%AF%E7%94%A8%E7%B3%BB%E7%BB%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;高可用系统&lt;/h2&gt;
&lt;h3 id=&quot;如何设计一个高可用系统？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E9%AB%98%E5%8F%AF%E7%94%A8%E7%B3%BB%E7%BB%9F%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何设计一个高可用系统？&lt;/h3&gt;
&lt;h2 id=&quot;限流&quot;&gt;&lt;a href=&quot;#%E9%99%90%E6%B5%81&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;限流&lt;/h2&gt;
&lt;h3 id=&quot;如何限流？说一下具体的实现？&quot;&gt;&lt;a href=&quot;#%E5%A6%82%E4%BD%95%E9%99%90%E6%B5%81%EF%BC%9F%E8%AF%B4%E4%B8%80%E4%B8%8B%E5%85%B7%E4%BD%93%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9F&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;如何限流？说一下具体的实现？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;限流可以认为服务降级的一种，限流就是限制系统的输入和输出流量以达到保护系统的目的。一般来说系统的吞吐量是可以被测算的，为了保证系统的稳定运行，一旦达到需要限制的阈值，就需要限制流量并采取一些措施以完成限制流量的目的。比如：延迟处理，拒绝处理，或者部分拒绝处理等等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;限流方法&quot;&gt;&lt;a href=&quot;#%E9%99%90%E6%B5%81%E6%96%B9%E6%B3%95&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;限流方法&lt;/h4&gt;
&lt;h5 id=&quot;计数器&quot;&gt;&lt;a href=&quot;#%E8%AE%A1%E6%95%B0%E5%99%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;计数器&lt;/h5&gt;
&lt;p&gt;控制单位时间内的请求数量。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;92309685345318340000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    /**
     * 最大访问数量
     */
    private final int limit = 10;
    /**
     * 访问时间差
     */
    private final long timeout = 1000;
    /**
     * 请求时间
     */
    private long time;
    /**
     * 当前计数器
     */
    private AtomicInteger reqCount = new AtomicInteger(0);

    public boolean limit() {
        long now = System.currentTimeMillis();
        if (now &lt; time + timeout) {
            // 单位时间内
            reqCount.addAndGet(1);
            return reqCount.get() &lt;= limit;
        } else {
            // 超出单位时间
            time = now;
            reqCount = new AtomicInteger(0);
            return true;
        }
    }
}`, `92309685345318340000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;concurrent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;atomic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 最大访问数量
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 访问时间差
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; timeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 请求时间
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 当前计数器
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt; reqCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; timeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 单位时间内&lt;/span&gt;
            reqCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAndGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; reqCount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; limit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 超出单位时间&lt;/span&gt;
            time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            reqCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AtomicInteger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;劣势：&lt;/p&gt;
&lt;p&gt;假设在 00:01 时发生一个请求，在 &lt;code class=&quot;language-text&quot;&gt;00:01 - 00:58&lt;/code&gt; 之间不在发送请求，在 00:59 时发送剩下的所有请求 n-1 (n 为限流请求数量)，在下一分钟的 00:01 发送 n 个请求，这样在 2 秒钟内请求到达了 2n - 1 个。&lt;/p&gt;
&lt;p&gt;设每分钟请求数量为 60 个，每秒可以处理 1 个请求，用户在 00:59 发送 60 个请求，在 01:00 发送 60 个请求 此时 2 秒钟有 120 个请求(每秒 60 个请求)，远远大于了每秒钟处理数量的阈值。&lt;/p&gt;
&lt;h5 id=&quot;滑动窗口&quot;&gt;&lt;a href=&quot;#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;滑动窗口&lt;/h5&gt;
&lt;p&gt;滑动窗口是对计数器方式的改进，增加一个时间粒度的度量单位，把一分钟分成若干等分（6 份，每份 10 秒），在每一份上设置独立计数器，在 &lt;code class=&quot;language-text&quot;&gt;00:00 - 00:09&lt;/code&gt; 之间发生请求计数器累加 1，当等分数量越大限流统计就越详细。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;26537285095234410000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`package com.example.demo1.service;

import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.IntStream;

public class TimeWindow {
    private ConcurrentLinkedQueue&lt;Long&gt; queue = new ConcurrentLinkedQueue&lt;Long&gt;();

    /**
     * 间隔秒数
     */
    private int seconds;

    /**
     * 最大限流
     */
    private int max;

    public TimeWindow(int max, int seconds) {
        this.seconds = seconds;
        this.max = max;

        /**
         * 永续线程执行清理 queue 任务
         */
        new Thread(() -&gt; {
            while (true) {
                try {
                    // 等待间隔秒数 - 1 执行清理操作
                    Thread.sleep((seconds - 1) * 1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                clean();
            }
        }).start();

    }

    public static void main(String[] args) throws Exception {
        final TimeWindow timeWindow = new TimeWindow(10， 1);

        // 测试 3 个线程
        IntStream.range(0， 3).forEach((i) -&gt; {
            new Thread(() -&gt; {
                while (true) {
                    try {
                        Thread.sleep(new Random().nextInt(20) * 100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    timeWindow.take();
                }
            }).start();
        });
    }

    /**
     * 获取令牌，并且添加时间
     */
    public void take() {
        long start = System.currentTimeMillis();
        try {
            int size = sizeOfValid();
            if (size &gt; max) {
                System.err.println(&amp;quot;超限&amp;quot;);
            }
            synchronized (queue) {
                if (sizeOfValid() &gt; max) {
                    System.err.println(&amp;quot;超限&amp;quot;);
                    System.err.println(&amp;quot;queue 中有 &amp;quot; + queue.size() + &amp;quot; 最大数量 &amp;quot; + max);
                }
                this.queue.offer(System.currentTimeMillis());
            }
            System.out.println(&amp;quot;queue 中有 &amp;quot; + queue.size() + &amp;quot; 最大数量 &amp;quot; + max);
        }
    }

    public int sizeOfValid() {
        Iterator&lt;Long&gt; it = queue.iterator();
        Long ms = System.currentTimeMillis() - seconds * 1000;
        int count = 0;
        while (it.hasNext()) {
            long t = it.next();
            if (t &gt; ms) {
                // 在当前的统计时间范围内
                count++;
            }
        }
        return count;
    }

    /**
     * 清理过期的时间
     */
    public void clean() {
        Long c = System.currentTimeMillis() - seconds * 1000;

        Long tl = null;
        while ((tl = queue.peek()) != null &amp;&amp; tl &lt; c) {
            System.out.println(&amp;quot;清理数据&amp;quot;);
            queue.poll();
        }
    }

}`, `26537285095234410000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;example&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;demo1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Iterator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;concurrent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeWindow&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; queue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 间隔秒数
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 最大限流
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;seconds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;max &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;/**
         * 永续线程执行清理 queue 任务
         */&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 等待间隔秒数 - 1 执行清理操作&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;seconds &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeWindow&lt;/span&gt; timeWindow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimeWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;， &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 测试 3 个线程&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;， &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token class-name&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InterruptedException&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    timeWindow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;take&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 获取令牌，并且添加时间
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;take&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sizeOfValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;超限&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;synchronized&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sizeOfValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;超限&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;queue 中有 &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 最大数量 &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;queue 中有 &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; 最大数量 &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sizeOfValid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Iterator&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; it &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; ms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; seconds &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 在当前的统计时间范围内&lt;/span&gt;
                count&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/**
     * 清理过期的时间
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; seconds &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; tl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tl &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;清理数据&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;leaky-bucket-漏桶&quot;&gt;&lt;a href=&quot;#leaky-bucket-%E6%BC%8F%E6%A1%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Leaky Bucket 漏桶&lt;/h5&gt;
&lt;p&gt;规定固定容量的桶，有水进入，有水流出。对于流进的水我们无法估计进来的数量、速度，对于流出的水我们可以控制速度。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;87888043628581900000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class LeakBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * 水流出去的速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;

    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.max(0， (nowSize - (now - time) * rate));
        time = now;
        if ((nowSize + 1) &lt; total) {
            nowSize++;
            return true;
        } else {
            return false;
        }

    }
}`, `87888043628581900000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LeakBucket&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 时间
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 总量
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; total&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 水流出去的速度
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; rate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 当前总量
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; nowSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        nowSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;， &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nowSize &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; rate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nowSize &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            nowSize&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id=&quot;token-bucket-令牌桶&quot;&gt;&lt;a href=&quot;#token-bucket-%E4%BB%A4%E7%89%8C%E6%A1%B6&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Token Bucket 令牌桶&lt;/h5&gt;
&lt;p&gt;规定固定容量的桶，token 以固定速度往桶内填充，当桶满时 token 不会被继续放入，每过来一个请求把 token 从桶中移除，如果桶中没有 token 不能请求。&lt;/p&gt;
&lt;div
              class=&quot;gatsby-code-button-container&quot;
              data-toaster-id=&quot;42202698345555304000&quot;
              data-toaster-class=&quot;gatsby-code-button-toaster&quot;
              data-toaster-text-class=&quot;gatsby-code-button-toaster-text&quot;
              data-toaster-text=&quot;复制成功&quot;
              data-toaster-duration=&quot;3500&quot;
              onClick=&quot;copyToClipboard(`public class TokenBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * token 放入速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;

    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.min(total， nowSize + (now - time) * rate);
        time = now;
        if (nowSize &lt; 1) {
            // 桶里没有 token
            return false;
        } else {
            // 存在 token
            nowSize -= 1;
            return true;
        }
    }

}`, `42202698345555304000`)&quot;
            &gt;
              &lt;div
                class=&quot;gatsby-code-button&quot;
                title=&quot;java&quot;
              &gt;
                &lt;span class=&quot;gatsby-code-button-language&quot;&gt;java&lt;/span&gt;
                &lt;span class=&quot;gatsby-code-button-text-icon&quot;&gt;复制代码&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber NaN&quot; class=&quot;language-java line-numbers&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TokenBucket&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 时间
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 总量
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; total&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * token 放入速度
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; rate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/**
     * 当前总量
     */&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Double&lt;/span&gt; nowSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        nowSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;total， nowSize &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; rate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nowSize &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 桶里没有 token&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 存在 token&lt;/span&gt;
            nowSize &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;工作中的使用&quot;&gt;&lt;a href=&quot;#%E5%B7%A5%E4%BD%9C%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;工作中的使用&lt;/h4&gt;
&lt;h5 id=&quot;spring-cloud-gateway&quot;&gt;&lt;a href=&quot;#spring-cloud