<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>code2life's blog</title>
        <link>https://code2life.top</link>
        <description>Joey Yang's blog</description>
        <lastBuildDate>Fri, 27 Mar 2026 03:17:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en-US</language>
        <image>
            <title>code2life's blog</title>
            <url>https://code2life.top/logo.png</url>
            <link>https://code2life.top</link>
        </image>
        <copyright>Copyright (c) 2023-present code2life</copyright>
        <item>
            <title><![CDATA[哪些Serverless Runtime能够安全执行用户代码（上）]]></title>
            <link>https://code2life.top/blog/0081-sandbox-research-1</link>
            <guid>https://code2life.top/blog/0081-sandbox-research-1</guid>
            <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="哪些serverless-runtime能够安全执行用户代码-上" tabindex="-1">哪些Serverless Runtime能够安全执行用户代码（上） <a class="header-anchor" href="#哪些serverless-runtime能够安全执行用户代码-上" aria-label="Permalink to &quot;哪些Serverless Runtime能够安全执行用户代码（上）&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="为什么需要安全沙箱" tabindex="-1">为什么需要安全沙箱 <a class="header-anchor" href="#为什么需要安全沙箱" aria-label="Permalink to &quot;为什么需要安全沙箱&quot;">&ZeroWidthSpace;</a></h2>
<p>平台型产品，总是会遇到各种各样的高级需求、定制需求，通用设计很难满足所有场景。</p>
<p>因此，平台型产品的归宿，一定是<strong>平台通用功能 + 用户自定义逻辑</strong>的方式来Scale out。</p>
<p>用户在平台上的自定义逻辑，简单需求可以用DSL、Expression Language解决，比如<a href="https://github.com/google/cel-spec" target="_blank" rel="noreferrer">Google CEL</a>、<a href="https://github.com/killme2008/aviatorscript" target="_blank" rel="noreferrer">AviatorScript</a>等等。</p>
<p>复杂到无法用DSL或Expression Language描述时，就需要上通用编程语言代码了。</p>
<p>这时，必然需要一个Runtime，来承载和运行用户的非可信代码。</p>
<h2 id="有哪些runtime方案-它们安全吗" tabindex="-1">有哪些Runtime方案，它们安全吗？ <a class="header-anchor" href="#有哪些runtime方案-它们安全吗" aria-label="Permalink to &quot;有哪些Runtime方案，它们安全吗？&quot;">&ZeroWidthSpace;</a></h2>
<p>业界有4类常见的运行自定义逻辑的方案：</p>
<ul>
<li>Script Engine路线：Java Rhino、GraalVM.js等等</li>
<li>MicroVM路线：AWS Lambda这类Serverless Runtime</li>
<li>容器路线：让用户代码运行在自定义的K8S Pod中</li>
<li>Custom Sandbox路线：基于Goja、QuickJS等Runtime二次封装</li>
</ul>
<p>除了第一种路线从设计上不是Secure by default，其他三类都是Secure by default。</p>
<p>但实际上是否真的安全，是一个复杂的问题。比如Script Engine从Runtime Security的角度不安全，但如果在使用这个功能的人上做限制，也能通过纵深防御(Defend in-depth)避免安全问题。</p>
<h3 id="为什么第一条路线scriptengine不安全" tabindex="-1">为什么第一条路线ScriptEngine不安全？ <a class="header-anchor" href="#为什么第一条路线scriptengine不安全" aria-label="Permalink to &quot;为什么第一条路线ScriptEngine不安全？&quot;">&ZeroWidthSpace;</a></h3>
<p>我们对“执行非可信代码”建立威胁模型，除了业务层的Sproofing, Tampering, Information Disclosure的威胁，Runtime自身的关键风险可以归为两方面问题：
DoS - 本质上是资源滥用
越权和逃逸 - 本质上是让非可信代码，拿到了宿主上的非预期的函数指针</p>
<p>和宿主共享同一个Runtime的Scripted Engine，DoS攻击是几乎没法防范的，写个while true轻松搞死宿主。</p>
<p>第二类逃逸越权威胁，在没有share nothing的设计下，也几乎无法防范，我们举3个语言的例子：</p>
<ul>
<li>Java JSR-223 ScriptEngine</li>
<li>Node.js VM</li>
<li>Python eval</li>
</ul>
<h4 id="java-rhino-scriptengine-逃逸案例" tabindex="-1">Java - Rhino ScriptEngine 逃逸案例 <a class="header-anchor" href="#java-rhino-scriptengine-逃逸案例" aria-label="Permalink to &quot;Java - Rhino ScriptEngine 逃逸案例&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> javax.script.ScriptEngine;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> javax.script.ScriptEngineManager;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.io.BufferedReader;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.io.InputStreamReader;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">throws</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Exception {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        String dynamicCode </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "new java.lang.ProcessBuilder[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">(java.lang.String[])</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">]([</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">env</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">]).start()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ScriptEngineManager scriptEngineManager </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ScriptEngineManager</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ScriptEngine scriptEngine </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scriptEngineManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getEngineByExtension</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (scriptEngine </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            System.out.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(String.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"no script engine of %s found"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Process process </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (Process) scriptEngine.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(dynamicCode);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">waitFor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        BufferedReader reader </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BufferedReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> InputStreamReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInputStream</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        StringBuilder output </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> StringBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        String line;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ((line </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">readLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            output.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(line).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(System.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lineSeparator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        System.out.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(output);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>新版本的rhino引入了ClassShutter限制script的Context中对java class的访问，通过ClassShutter能解决吗？</p>
<p>还是不行，对Hacker来说只是多走一步路的问题，任何对象调一下 getClass() 拿到reflection包就全部沦陷了。</p>
<p>因此，一个稍微安全一些的ScriptEngine，至少要禁掉所有可能导致反射和拿到constructor的代码路径</p>
<p>有一篇文章详细讲了<a href="https://codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-java/" target="_blank" rel="noreferrer">怎么在Rhino上构建安全沙箱</a>，但在不安全的基础上再缝缝补补，也存在很大的隐患。</p>
<p>因此，这些直接在宿主上跑的<strong>非隔离型ScriptEngine</strong>都有这个问题，JSR 223规范下这些常见的包都不能暴露给用户用：</p>
<ul>
<li>org.graalvm.js:js</li>
<li>org.graalvm.js:js-scriptengine</li>
<li>org.python:jython-standalone</li>
<li>org.codehaus.groovy:groovy-jsr223</li>
<li>org.codehaus.groovy:groovy-json</li>
</ul>
<p>另外，SpringEL也不是Secure by default，默认能访问的类和方法太多了，需要额外配置才能禁用访问。</p>
<h4 id="node-js-vm-runinnewcontext-逃逸示例" tabindex="-1">Node.js vm.runInNewContext 逃逸示例 <a class="header-anchor" href="#node-js-vm-runinnewcontext-逃逸示例" aria-label="Permalink to &quot;Node.js vm.runInNewContext 逃逸示例&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> vm</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"vm"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">vm.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">runInNewContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"this.constructor.constructor('return process')().exit()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">vm.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">runInNewContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"this.constructor.constructor('return require')()('fs')"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h4 id="python-eval-逃逸示例" tabindex="-1">Python eval 逃逸示例 <a class="header-anchor" href="#python-eval-逃逸示例" aria-label="Permalink to &quot;Python eval 逃逸示例&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-python vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"__builtins__.__dict__['__import__']('os').environ"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h3 id="microvm路线真的安全吗" tabindex="-1">MicroVM路线真的安全吗？ <a class="header-anchor" href="#microvm路线真的安全吗" aria-label="Permalink to &quot;MicroVM路线真的安全吗？&quot;">&ZeroWidthSpace;</a></h3>
<p>AWS Lambda和Fargate背后的runtime，都是一个Rust写的MicroVM - <a href="https://github.com/firecracker-microvm/firecracker" target="_blank" rel="noreferrer">Firecraker</a>，Firecraker这个项目的技术水平非常高，把创建虚拟机的时间直接干到了Container同一个水平，我们可以简单理解成：</p>
<p>Lambda的安全性 = 虚拟机的安全性 = 给用户创建一台EC2的安全性。</p>
<p>可见，AWS Lambda是一个“既要又要”的产物，既要给用户最大的自定义程度，又要追求极致的安全性。</p>
<p>如果不深入思考，很容易陷入AWS Lambda安全又弹性的营销陷阱。</p>
<p>理性思考一下我们的应用场景 -- “给我们的客户运行代码”：</p>
<ul>
<li>除了发一台物理服务器给客户，VM Level的沙箱已经是最底层的沙箱了</li>
<li>最底层的OS沙箱，相当于给了一个完整的Linux内核，暴露了最多的系统调用</li>
<li>最多的系统调用意味着：如果不加一层应用层沙箱，用户代码可以做任何事情</li>
<li>用户能做任何事情，要么意味着拿你的Lambda资源挖挖矿，造成DoS或者巨额Bills；要么意味着顺藤摸瓜SSRF搞你的服务</li>
</ul>
<p>用Lambda执行用户代码，一般ScriptEngine这类高层Runtime没法调用的底层CPU指令，在Lambda中用户却可以任意构造，也就是说，<strong>所有的VM逃逸漏洞或幽灵漏洞一旦有EXP，暴露在外面的Lambda，就是黑客免费的试验场</strong>。</p>
<p>Lambda跑的结果，总归要和内部服务交互的，一旦有Lambda的网络和AWS权限(VPC Subnet / AWS IAM) 出问题，或者通信机制有问题，黑客可以摸到整个产线。</p>
<p>这个风险在DataDog的Serverless研究报告中也有描述：<a href="https://www.datadoghq.com/state-of-serverless/#8" target="_blank" rel="noreferrer">65%的组织把Lambda使用了其他服务同一个VPC</a>而不是没有任何网络权限的隔离VPC。</p>
<p>你说不对啊，我们只要在信任边界上把把关，确保让AWS Lambda可以让Execution IAM Role权限最小化，可以让Lambda运行在独立的VPC设置独立的Security Group啊？</p>
<p>且不说我们公司目前IAM Role和VPC/SecurityGroup管理是多么的手动，<strong>即使全部能自动化，灵活性和管控粒度也太粗了</strong>，总不可能给每个Function搞一套Security Group吧。</p>
<p>至此，可以得出结论：<strong>Lambda对于AWS自己是安全的，如果要基于Lambda再来构建第二层非可信代码执行沙箱，攻击面大，控制粒度粗，并不安全</strong>。</p>
<p><strong>除非，再做一个资源限额和计费/限流层防DoS + 再做一层应用层Runtime隔离危险操作</strong>。</p>
<p>既然MicroVM路线不结合其他方案，不能保障运行用户代码的安全性，那容器路线更不可能满足安全需求了，相比于VM，容器多出了一个共享OS内核可能导致宿主机级联失败(Cascade Failure)的风险，还要再多做一些<strong>租户分级和风险识别</strong>的事情，结合应用层沙箱和使用限额，才有可能安全的执行用户自定义代码。</p>
<h3 id="那么-只剩下custom-sandbox路线了" tabindex="-1">那么，只剩下Custom Sandbox路线了 <a class="header-anchor" href="#那么-只剩下custom-sandbox路线了" aria-label="Permalink to &quot;那么，只剩下Custom Sandbox路线了&quot;">&ZeroWidthSpace;</a></h3>
<p>开源社区找不到完整的非可信代码执行平台。AWS只开源了Firecraker运行时，Serverless公司大多只开源了SDK和Runtime。</p>
<p>比较可行的方案是：用现存成熟的Runtime，<strong>减去所有OS Level API，加上业务能力层API，再把威胁模型的其他风险处理好</strong>。</p>
<p>我们再结合”托管和执行外部用户非可信代码“的威胁模型，可以得出理想方案的两个基本要求：</p>
<ol>
<li>在沙盒中只提供充分且必要的能力层，避免能力溢出，导致用户脱离预期的用例，导致DoS风险</li>
<li>宿主与沙盒之间必须是<strong>两种不同的Runtime, share nothing</strong>。必须要定义通信协议而非直接函数调用，才能从根本上解决 宿主运行时的高权限函数指针 暴露给用户的风险，通俗的说，就是要<strong>避免宿主被夺舍</strong>。</li>
</ol>
<p>带着这个需求不难发现，<strong>Secure CodeSandbox这枚硬币的反面，就是Serverless Runtime</strong>，都是用来执行用户的非可信代码。</p>
<p>因此，我们只要寻找最<strong>适合集成和扩展的Serverless Runtime即可</strong>满足<strong>构建平台型产品扩展点的需求</strong>。</p>
<p>市面上有大量Serverless Runtime项目，可以使用或借鉴，具体分析请看下篇。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[哪些Serverless Runtime能够安全执行用户代码（下）]]></title>
            <link>https://code2life.top/blog/0081-sandbox-research-2</link>
            <guid>https://code2life.top/blog/0081-sandbox-research-2</guid>
            <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="哪些serverless-runtime能够安全执行用户代码-下" tabindex="-1">哪些Serverless Runtime能够安全执行用户代码？（下） <a class="header-anchor" href="#哪些serverless-runtime能够安全执行用户代码-下" aria-label="Permalink to &quot;哪些Serverless Runtime能够安全执行用户代码？（下）&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="有哪些serverless-runtime可以用" tabindex="-1">有哪些Serverless Runtime可以用？ <a class="header-anchor" href="#有哪些serverless-runtime可以用" aria-label="Permalink to &quot;有哪些Serverless Runtime可以用？&quot;">&ZeroWidthSpace;</a></h2>
<p>Serverless已经发展多年了，我们可以从宿主提供的API这个维度，给所有Runtime分个类，鸟瞰Serverless Runtime生态。</p>
<p><strong>第一类是操作系统沙箱</strong></p>
<p>基于MicroVM或Container，提供底层OS-level API，隔离粒度是OS内核级别或进程级别。</p>
<p><strong>第二类是WebAssembly沙箱</strong></p>
<p>基于WASM Runtime封装，提供WASI/WASIX系统调用或应用开发API，隔离粒度在进程线程级别。</p>
<p><strong>第三类是基于V8解释器的应用层沙箱</strong></p>
<p>基于一些动态类型编程语言的Runtime封装，提供可以直接写业务的API，隔离粒度比线程级别还细。主流方案几乎都是基于V8 Engine的Isolate实现的。</p>
<p>随着<strong>宿主运行时 封装层次提高和隔离粒度细化</strong>，相比于Serverful的VM/BareMetal，这几类Serverless方案在<strong>多租户并发</strong>情况下，都产生了效率质变。</p>
<table tabindex="0">
<thead>
<tr>
<th>Runtime Type​</th>
<th>Code Footprint​</th>
<th>Basline Memory​</th>
<th>Code-start Time​</th>
<th>Context Switching​</th>
</tr>
</thead>
<tbody>
<tr>
<td>Virtual Machine​</td>
<td>1-10 GB​</td>
<td>1GB​</td>
<td>10s​</td>
<td>Low​(System Space)​</td>
</tr>
<tr>
<td>MicroVM/Container​</td>
<td>100MB​</td>
<td>100MB​</td>
<td>500ms​</td>
<td>Medium​(System Space)​</td>
</tr>
<tr>
<td>WebAssembly​</td>
<td>1-10MB​</td>
<td>&lt;10MB​</td>
<td>&lt;5ms​</td>
<td>Extreme​(User Space)​</td>
</tr>
<tr>
<td>V8 Isolates​</td>
<td>&lt;1MB​</td>
<td>&lt;5MB​</td>
<td>&lt;5ms​</td>
<td>Extreme​(User Space)​</td>
</tr>
</tbody>
</table>
<p>注：这里讨论的几种Serverless Runtime的技术方向，都属于Serverless中的FaaS - Function as a Service子领域。</p>
<p>广义的Serverless定义还包括BaaS -- Backend as a service。而BaaS一般是用来快速搭建后台或者全栈应用，和低代码平台关联更密切，比如Back4App, Firebase, Supabase。</p>
<p>我们的场景是”给SaaS平台增加自定义逻辑的扩展点“，需求就是FaaS，而不是BaaS，BaaS类Serverless不在讨论范围。</p>
<h3 id="操作系统级别的沙箱" tabindex="-1">操作系统级别的沙箱 <a class="header-anchor" href="#操作系统级别的沙箱" aria-label="Permalink to &quot;操作系统级别的沙箱&quot;">&ZeroWidthSpace;</a></h3>
<p>传统VM冷启动时间过长，无法满足Serverless的弹性要求，业界一般用MicroVM或Container来实现Serverless runtime</p>
<ul>
<li>MicroVM，每个实例有独立OS内核</li>
<li>Containers，同机器的实例间共享OS内核</li>
</ul>
<p>MicroVM方向的典型代表，是应用最广泛的AWS Lambda。再加上几大云厂商各自的Serverless产品，垄断了这条路线的大部分市场。</p>
<p>开源方案中，大多是基于Container实现的，比如Knative和Kubeless，但在Serverless大市场来看，是小众方案。这两个方案也可以结合起来用，KataContainer就是以容器做编排，底层CRI换成MicroVM实现。</p>
<p>这类方案中规中矩，但基于现有的<strong>虚拟化和容器化</strong>技术，这条路不可能把多租户调度细到线程以下。也就是说，即使Firecraker这类MicroVM把<strong>Guest OS做到了用户态</strong>，多租户的用户代码调度产生的上下文切换，还是在内核态。</p>
<p><img src="https://firecracker-microvm.github.io/img/diagram-desktop@3x.png" alt=""></p>
<p>这类方案用在公司内部租户较少风险可控的场景挺不错，但显然不是作为”<strong>SaaS系统的客户自定义扩展点</strong>“这个场景的最优解：</p>
<ul>
<li>做不到用户态调度，资源利用率提不上去；</li>
<li>如果不组合使用后面两类Runtime，分发的制品是OS或容器镜像，冷启动效率提不上去；</li>
<li>OS-level API能力溢出，暴露给外部用户，攻击面不好控制。</li>
</ul>
<p>在有根本性缺陷的路上走下去一定会限制长期发展，我们首先排除掉MicroVM/Containers，重点关注后面两类<strong>控制粒度更细的Runtime</strong>。</p>
<h3 id="webassembly沙箱" tabindex="-1">WebAssembly沙箱 <a class="header-anchor" href="#webassembly沙箱" aria-label="Permalink to &quot;WebAssembly沙箱&quot;">&ZeroWidthSpace;</a></h3>
<p>WebAssembly这几年在后端非常热闹，除了字节码联盟(ByteCode Alliance)极力推广，CNCF和一众创业公司也都非常看好。CNCF甚至单开了<a href="https://landscape.cncf.io/?group=wasm&amp;view-mode=grid" target="_blank" rel="noreferrer">一级目录</a>来汇总Wasm技术生态。</p>
<p>WASM字节码是一个真正中立的、开放的字节码系统，已经是跨越编程语言鸿沟的标准答案。
那么，给WASM加上系统调用，不就能<strong>跨越操作系统鸿沟</strong>了么？</p>
<p>有了系统调用能力，不就可以轻松实现任何功能的Serverless Runtime了吗？</p>
<p>的确，在WASM Serverless生态已经有了不少探索者，其中有两家公司值得持续关注：</p>
<ul>
<li>2021年，Helm, OAM, Krustlet的项目创始人，Matt Butcher离开微软，成立了Fermyon公司，专注开发基于WebAssembly的Serverless平台。</li>
<li>2023年5月，Wasmer公司在WASI的基础上提出了<a href="https://wasix.org/" target="_blank" rel="noreferrer">WASIX</a>，实现了完整的System Interface。</li>
</ul>
<blockquote>
<p>&quot;第一代云计算是虚拟化、第二代云计算是容器化、第三代云计算是WebAssembly&quot;。</p>
<p>-- Matt Butcher, Fermyon Founder &amp; CEO</p>
</blockquote>
<blockquote>
<p>If WASM+WASI existed in 2008, we wouldn't have needed to created Docker. That's how important it is.
Webassembly on the server is the future of computing. A standardized system interface was the missing link. Let's hope WASI is up to the task!</p>
<p>-- Solomon Hykes, Docker Founder</p>
</blockquote>
<p><img src="https://filecdn.code2life.top/Serverless%20Runtime%20Inquiry.png" alt=""></p>
<p>可见，WASM沙箱介于前两者之间，既是语言无关的通用方案，又能提供从<strong>系统层到应用层的API，还足够轻量</strong>。</p>
<p>主流编程语言都有不少优秀的、成熟的WASM Runtime项目。WASM的繁荣，无意间拍死了相同愿景的Oracle GraalVM，用WASM Runtime做Serverless Runtime，性能接近原生应用，还不会被编程语言和云厂商锁死，看上去非常完美。</p>
<p>但我们知道，<strong>工程是关于Trade-off的</strong>。</p>
<p><strong>那么，代价是什么</strong>？</p>
<p>首先，WASM运行不同语言的代码时，并没有想象的那么无缝。由于每种语言自己的内存编码方式和WASM不一样，跨语言执行时需要引入Binding胶水层做转换，一些性能会在内存拷贝时损耗掉，另一个问题是，编写这层Binding比较耗时，好在最近有个开源项目 Extism 能够部分解决这个问题。第三个问题，也是对开发者最大的问题，是WASM不容易Debug，C/C++还好，Chrome DevTools可以用，但WASM里面再启动一个其他语言的Runtime支持Python/JS的场景就麻烦了。</p>
<p>其次，编译成WASM和编程语言自己的FFI(Foreign Function Interface)生态是竞争关系，一些应用层的依赖如果用调用了更底层的动态链接库，上层迁移WASM依赖底层库也能编译成WASM。</p>
<p>这些问题导致了WASM生态很不健全，一边是Runtime实现很多（字节码简单直白，Carl也顺手写了一个），另一边是以WASM形态提供的三方库很少。</p>
<p>综上，WASM生态目前的成熟度，不足以支撑复杂业务的规模落地，但未来可期。</p>
<h3 id="v8-isolates沙箱" tabindex="-1">V8 Isolates沙箱 <a class="header-anchor" href="#v8-isolates沙箱" aria-label="Permalink to &quot;V8 Isolates沙箱&quot;">&ZeroWidthSpace;</a></h3>
<p>V8 Engine对于Java/Go技术栈的同学听上去可能有些陌生，但它可能是从2008年问世以来，世界上运行次数最多的Runtime。</p>
<ul>
<li>所有Chromium内核的浏览器跑的JS Runtime是V8；</li>
<li>所有Node.js/Deno的后端服务和前端工具链也是V8；</li>
</ul>
<p>根据Datadog 2023年的Serverless调研报告，占据40%以上市场的Node.js也是V8。</p>
<p><img src="https://filecdn.code2life.top/serverless-lang-trending.png" alt=""></p>
<p><strong>V8 Isolates</strong>是V8 Engine的关键概念，可以简单理解成浏览器中的Tab。</p>
<p>浏览器Tab和Serverless Runtime有什么关系呢？带着这个问题，我们再审视一下浏览器执行引擎。</p>
<p>浏览器为了<strong>让用户尽可能获得接近原生应用的体验</strong>：</p>
<ul>
<li>优化了代码打包和各种网络环境下的分发性能</li>
<li>优化了冷启动时间</li>
</ul>
<p>浏览器为了支持<strong>打开大量不同网站的Tab的性能</strong>，还要互相完全隔离：</p>
<ul>
<li>优化了计算资源调配和多租户的配额</li>
<li>从设计上就保障安全隔离</li>
</ul>
<p>甚至浏览器为了<strong>兼容各种编程语言</strong>、支持<strong>高性能原生代码执行</strong>，还自带了WASM Runtime！</p>
<p><strong>我们惊讶的发现，浏览器的执行引擎和一个安全高效的Serverless Runtime的需求，竟然是完全一致的</strong>！</p>
<p>既然浏览器引擎本擎，就满足了Serverless Runtime的需求，那为什么还要AWS Lambda这种MicroVM + Node.js 两层Runtime的蹩脚架构？只用V8 Isolates不就够了吗？</p>
<p>对的，当然可以。</p>
<p>以上观点来自创造了Protobuf Protocol(V2)、Cap'n Protol, Cloudflare Workers的巨佬<a href="https://github.com/kentonv" target="_blank" rel="noreferrer">Kenton Varda</a>。</p>
<p>Cloudflare Workers是这类Serverless Runtime的典型代表。这类Runtime与业务层关联更紧密，要什么能力就直接调用，没有那么多底层的抽象概念，开发者体验做的很好。</p>
<p><img src="https://filecdn.code2life.top/cloudflare-deploy-sc.png" alt=""></p>
<p>但是，我们还要回答一个问题，<strong>抛弃了MicroVM/Container这层OS-level Runtime的隔离，还足够安全吗</strong>？</p>
<p><strong>Kenton作为Cloudflare Workerd Founder，自己用<a href="https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/" target="_blank" rel="noreferrer">12页安全模型分析</a>，完美回答了这个问题</strong>。</p>
<p>我们划下重点：</p>
<ul>
<li>V8 Isolates自带的机制，结合一些简单的Eviction策略，就能实现资源隔离和高效调度</li>
<li>平台主动升级V8，不仅借了Google的顶级安全团队的力量，而且避免了传统Serverless Runtime模式下，厚重的Guest层的漏洞没人管的困境</li>
<li>主动对不同用户的代码进行动态监控、分级、分区调度，定时轮换对内存洗牌</li>
<li>不暴露POSIX系统调用，尽可能确保代码运行的确定性，有效缓解了幽灵漏洞这类边信道攻击</li>
</ul>
<p><img src="https://filecdn.code2life.top/unique-mitigation-workderd.png" alt=""></p>
<p>但深入阅读Cloudflare Workers的<a href="https://github.com/cloudflare/workerd" target="_blank" rel="noreferrer">workerd源码</a>后，发现Cloudflare Workers也有一些局限性，比如：</p>
<ul>
<li>深度绑定了Cloudflare的其他云产品</li>
<li>对TypeScript的支持不够好</li>
<li>使用Pyodide支持WASM运行Python解释器仍早期实验阶段。</li>
</ul>
<p>个人认为真正把V8 Isolates这条路线走到极致的，是一个2023年开源的新项目，<a href="https://github.com/supabase/edge-runtime" target="_blank" rel="noreferrer">Supabase Edge Runtime</a>。</p>
<p>EdgeRuntime的架构和设计思路完全致敬了Cloudflare Workers，但最大的区别在于：</p>
<ul>
<li>Cloudflare Workers基于 Node.js API + C++实现</li>
<li>EdgeRuntime基于 Deno API + Rust实现</li>
</ul>
<p>注：基于V8 Isolates的方案，还有一个更早的isolated-vm。比V8 Isolates更轻的方案中，有个有趣的Serverless runtime项目是AWS前不久开源的LLRT，LLRT是一个基于QuickJS的非常轻量的Runtime。但QuickJS其实只是冷启动Quick，对于每个Request创建一个Worker的场景比较合适，如果是池化后长期跑，性能与V8差1-2个数量级。</p>
<p>另一个常见的选项是Goja，压测工具K6就是用Goja实现的大量Virtual User并行执行自定义的压测脚本的。Goja实际使用下来，没有大问题，但对ECMA新标准的支持不足，项目活跃度比较差，也没有内嵌WASM运行时，所以支持不了除了JS以外的语言。</p>
<h3 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h3>
<p>我们再从Host和Guest上分别有什么，来总结这几类Serverless Runtime方案的差异。</p>
<p>拿 #1(VM/Containers) 和 #3 (V8 Isolates) 对比，可以发现 VM/Containers是轻Host重Guest的，而Isolates方案是重Host轻Guest的。Guest越轻，攻击面越小，用户也能更聚焦自己的业务，而非Runtime的底层概念。</p>
<p><img src="https://filecdn.code2life.top/compare-vm-v8.png" alt=""></p>
<p>拿 #2 (WASM )和 #3 (V8 Isolates) 对比，可以发现WASM方案的架构更合理，但复杂度最高。</p>
<p><img src="https://filecdn.code2life.top/compare-wasm-v8.png" alt=""></p>
<p>最后，一句话总结一下这三条Serverless Runtime技术路线的特点：</p>
<ul>
<li>V8-based runtime：把浏览器Tab玩出了艺术</li>
<li>VM/Container-based runtime: 缝缝补补，小破小立</li>
<li>WASM-based runtime: 思想超前，大破大立</li>
</ul>
<h2 id="我们该选哪个" tabindex="-1">我们该选哪个？ <a class="header-anchor" href="#我们该选哪个" aria-label="Permalink to &quot;我们该选哪个？&quot;">&ZeroWidthSpace;</a></h2>
<p>这三种路线该什么选呢？我们回到最开始的目标：”构建SaaS平台自定义扩展点“，展开分析Serverless Runtime / Code Sandbox的需求，看看跟这些技术方案的匹配程度。</p>
<table tabindex="0">
<thead>
<tr>
<th>需求​</th>
<th>指标​</th>
<th>预期效果​</th>
</tr>
</thead>
<tbody>
<tr>
<td>安全需求：安全沙箱​</td>
<td>CPU/Mem资源配额能力​</td>
<td>能够分别限制CPU I/O等待时间和计算时间 (缓解while true DoS问题)​</td>
</tr>
<tr>
<td>​</td>
<td>细粒度的网络访问控制能力​</td>
<td>Deny All Network Access by default​</td>
</tr>
<tr>
<td>​</td>
<td>细粒度的文件系统访问控制能力​</td>
<td>Deny All FS Access by default​</td>
</tr>
<tr>
<td>​</td>
<td>细粒度的宿主函数指针访问控制能力​</td>
<td>Deny All System Interface by default​</td>
</tr>
<tr>
<td>​</td>
<td>处理安全问题的活跃度​</td>
<td>对安全问题能在半月内响应。1月内修复​</td>
</tr>
<tr>
<td>用户需求：工具链与开发体验​</td>
<td>调试测试工具成熟度​</td>
<td>有单测和Mock工具链​有断点调试工具链​</td>
</tr>
<tr>
<td>​</td>
<td>部署运维工具成熟度​</td>
<td>能够一键部署应用/函数，无需配置就有所有关键指标和对应的告警​</td>
</tr>
<tr>
<td>​</td>
<td>生态丰富度​</td>
<td>能够使用所支持语言自己的标准库和大部分主流的三方库​</td>
</tr>
<tr>
<td>​</td>
<td>多语言支持​</td>
<td>至少支持JS/Python两种语言，其他静态类型语言nice-to-have​</td>
</tr>
<tr>
<td>平台需求：稳定性/性能/成本​</td>
<td>冷启动性能​</td>
<td>&lt; 5ms​</td>
</tr>
<tr>
<td>​</td>
<td>运行时性能​</td>
<td>相比于所支持的编程语言原生运行时，性能下降不能超过1个数量级​</td>
</tr>
<tr>
<td>​</td>
<td>租户调度的上下文切换效率​</td>
<td>&lt; 1ms, 尽可能在用户态实现租户切换​</td>
</tr>
<tr>
<td>​</td>
<td>开发和集成的成本​</td>
<td>Runtime能够支持在不fork项目的情况下，扩展或定制宿主提供的能力API​</td>
</tr>
<tr>
<td>​</td>
<td>计算资源成本​</td>
<td>运行成本低于﻿AWS Lambda价格的50%​</td>
</tr>
<tr>
<td>​</td>
<td>技术成熟度/采纳度​</td>
<td>该Runtime能找到大规模落地案例​</td>
</tr>
<tr>
<td>​</td>
<td>问题排查工具链成熟度​</td>
<td>有完善的Troubleshooting工具链和问题排查案例​</td>
</tr>
<tr>
<td>​</td>
<td>更换成本​</td>
<td>有办法更换迭代底层Runtime的实现，避免云厂商的Vendor-lockin​</td>
</tr>
</tbody>
</table>
<p>三种技术路线的主流方案效果对比，<strong>结论是Supabase EdgeRuntime</strong>胜出。</p>
<table tabindex="0">
<thead>
<tr>
<th>指标\Runtime选型​</th>
<th>Supabase EdgeRuntime​(V8 Isolates)​</th>
<th>Cloudflare Workerd​(V8 Isolates)​</th>
<th>Wasmer​(multiple WASM backends)​</th>
<th>Firecraker​(MicroVM)​</th>
<th>AWS Lambda​(Hosted MicroVM)​</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU/Mem资源配额能力​</td>
<td>Good​</td>
<td>Perfect (﻿refer this)​</td>
<td>Good​</td>
<td>Good​</td>
<td>Good​</td>
</tr>
<tr>
<td>细粒度的网络访问控制能力​</td>
<td>Good​(can disable all net access)​</td>
<td>Good​(need extent the proxy interface)​</td>
<td>Good​(disabled by default)​</td>
<td>N/A​</td>
<td>Good​(VPC configurable)​</td>
</tr>
<tr>
<td>细粒度的文件系统访问控制能力​</td>
<td>Good​(no access by default)​</td>
<td>Good​(no access by default)​</td>
<td>Good​(config with wasmer.toml)​</td>
<td>N/A​</td>
<td>Good​(/tmp ephemeral storage)​</td>
</tr>
<tr>
<td>细粒度的宿主函数指针访问控制能力​</td>
<td>Perfect​(add them in main worker)​</td>
<td>Perfect​(specify wranger.toml)​</td>
<td>Perfect​(specify dependency)​</td>
<td>N/A​</td>
<td>N/A​</td>
</tr>
<tr>
<td>处理安全问题的活跃度​</td>
<td>Active​</td>
<td>Active​</td>
<td>Active​</td>
<td>Active​</td>
<td>Active​</td>
</tr>
<tr>
<td>调试测试工具成熟度​</td>
<td>Good​</td>
<td>Perfect​</td>
<td>Not Good​</td>
<td>Good​</td>
<td>Good​</td>
</tr>
<tr>
<td>部署运维工具成熟度​</td>
<td>Not Good​</td>
<td>Not Good​</td>
<td>Good​</td>
<td>Not Good​</td>
<td>Good​</td>
</tr>
<tr>
<td>生态丰富度​</td>
<td>Good​</td>
<td>Good​</td>
<td>Not Good​</td>
<td>All Supported​</td>
<td>All Supported​</td>
</tr>
<tr>
<td>多语言支持​</td>
<td>Good (JS/TS/PY)​</td>
<td>Good (JS/TS/PY)​</td>
<td>Good (Major popular langs)​</td>
<td>All Supported​</td>
<td>All Supported​</td>
</tr>
<tr>
<td>冷启动性能​</td>
<td>Perfect​</td>
<td>Perfect​</td>
<td>Perfect​</td>
<td>Good​</td>
<td>Good​</td>
</tr>
<tr>
<td>运行时性能​</td>
<td>Perfect (for TS/JS)​Good (for Py)​</td>
<td>Perfect (for TS/JS)​Good (for Py)​</td>
<td>Good​</td>
<td>Perfect​</td>
<td>Perfect​</td>
</tr>
<tr>
<td>租户调度的上下文切换效率​</td>
<td>High​</td>
<td>High​</td>
<td>High​</td>
<td>Medium​</td>
<td>Medium​</td>
</tr>
<tr>
<td>开发和集成的成本​</td>
<td>Cheap​</td>
<td>High​</td>
<td>High​</td>
<td>High​</td>
<td>High​</td>
</tr>
<tr>
<td>计算资源成本​</td>
<td>Cheap​</td>
<td>Cheap​</td>
<td>Cheap​</td>
<td>Expensive​</td>
<td>Expensive​</td>
</tr>
<tr>
<td>技术成熟度/采纳度​</td>
<td>Medium​</td>
<td>High​</td>
<td>Low​</td>
<td>High​</td>
<td>High​</td>
</tr>
<tr>
<td>问题排查工具链成熟度​</td>
<td>Medium​</td>
<td>High​</td>
<td>Low​</td>
<td>Medium​</td>
<td>High​</td>
</tr>
<tr>
<td>更换Runtime难度​</td>
<td>Medium​</td>
<td>Medium​</td>
<td>Easy​</td>
<td>N/A​</td>
<td>N/A​</td>
</tr>
</tbody>
</table>
<h2 id="对edgeruntime的详细评估" tabindex="-1">对EdgeRuntime的详细评估 <a class="header-anchor" href="#对edgeruntime的详细评估" aria-label="Permalink to &quot;对EdgeRuntime的详细评估&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="源码阅读" tabindex="-1">源码阅读 <a class="header-anchor" href="#源码阅读" aria-label="Permalink to &quot;源码阅读&quot;">&ZeroWidthSpace;</a></h3>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>main.rs → commands.rs → server.rs → worker_ctx.rs →</span></span>
<span class="line"><span>| → worker_pool.rs → worker.rs → deno_runtime.rs </span></span>
<span class="line"><span>|                                 → sb_core/permissions.rs</span></span>
<span class="line"><span>|                                 → sb_fs/virtual_fs.rs | static_fs.rs</span></span>
<span class="line"><span>| → supervisor.rs</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><ul>
<li>启动逻辑大部分在 worker_ctx.rs 中，组装参数，创建pool，worker，supervisor等等；</li>
<li>处理请求的逻辑，从server.rs 的 accept_stream() 开始，一层一层把消息传到main worker / user worker 中，完全用channel进行消息传递，没有函数指针传递；</li>
<li>permissions.rs主要限制网络和deno core中非fs部分的权限 (由于需要细粒度的fs控制，deno core本身不够动态，所以fs的权限在这里放过，专门由sb_fs处理);</li>
<li>virtual_fs.rs用于 main worker，有完整的FS权限； static_fs.rs 用于 user worker, 啥权限都没有，read_file_sync的逻辑只有根据net access 来判断是否允许加载import依赖的逻辑，非mem fs路径的全部返回path not found, 测试没有办法读到任何路径。</li>
</ul>
<h3 id="渗透测试" tabindex="-1">渗透测试 <a class="header-anchor" href="#渗透测试" aria-label="Permalink to &quot;渗透测试&quot;">&ZeroWidthSpace;</a></h3>
<p>对User Worker做了一些渗透测试，发现一个安全问题，给他们提了个Issue。
<a href="https://github.com/supabase/edge-runtime/issues/340" target="_blank" rel="noreferrer">https://github.com/supabase/edge-runtime/issues/340</a></p>
<h3 id="性能测试" tabindex="-1">性能测试 <a class="header-anchor" href="#性能测试" aria-label="Permalink to &quot;性能测试&quot;">&ZeroWidthSpace;</a></h3>
<p>单机启动100个Worker，混合并发请求压测。压测代码如下：</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'main function started'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createWorker</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">servicePath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> noModuleCache</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> importMapPath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> envVars</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">as</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">][];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> forceCreate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> netAccessDisabled</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> workerTimeoutMs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 60</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 60</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> cpuTimeSoftLimitMs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> cpuTimeHardLimitMs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 3000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> memoryLimitMb</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 16</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> EdgeRuntime.userWorkers.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    servicePath,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    memoryLimitMb,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    workerTimeoutMs,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    noModuleCache,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    importMapPath,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    envVars,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    forceCreate,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    netAccessDisabled,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    cpuTimeSoftLimitMs,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    cpuTimeHardLimitMs,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> userWorkers</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> any</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> userWorkersPromise</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">as</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">unknown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>[]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> start500UserWorkers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> baseDir</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Deno.env.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"HOME"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '/SourceCode/edge-runtime-benchmark'</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 500</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> tempDir</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> baseDir </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `/function_${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        userWorkersPromise.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">createWorker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tempDir).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">worker</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> userWorkers.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(worker)))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // await createWorker(tempDir).then(worker => userWorkers.push(worker))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start500UserWorkers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'create 500 workers'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">await</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">all</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(userWorkersPromise)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">timeEnd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'create 500 workers'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Deno.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">serve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">req</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Request</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // request with header: functionId: 0-1999</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> worker</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> userWorkers[req.headers.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'functionId'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> unknown</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">worker) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Response.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">json</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({ error: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, msg: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'function not exists'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}, { status: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> worker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br></div></div><p>K6测试代码</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> http </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "k6/http"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { check } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "k6"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> options</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    scenarios: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        simple: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            executor: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'constant-vus'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            vus: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            duration: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'1m'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> functionIdValue</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">floor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 500</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> res</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> http.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'http://localhost:8989/'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        headers: {</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            'functionId'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">functionIdValue</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    check</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(res, {</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        'status is 200'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">r</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>MacPro M1的测试结果，单机100个Worker的QPS是4784。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">k6</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> benchmark-100</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">          /\</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">‾‾</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> /‾‾/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   /‾‾/</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     /\</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  \ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  /</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  \/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    \ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">     (   </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   ‾‾</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">          \ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">\</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  \ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">‾</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)  </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> __________</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> \__\</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \_</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">____/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .io</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     execution:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> local</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        script:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> benchmark-2000.ts</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        output:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> -</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     scenarios:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (100.00%) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> scenario,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> max</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> VUs,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1m30s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> max</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> duration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (incl. </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">graceful</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">              *</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> simple:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> looping</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> VUs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1m0s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (gracefulStop: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">30s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     ✓</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 200</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     checks.........................:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 100.00%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ✓</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 287124</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      ✗</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     data_received..................:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 46</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> MB</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">   770</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kB/s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     data_sent......................:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 28</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> MB</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">   459</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kB/s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_blocked...............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=5.12µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  min=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     med=1µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     max=13.43ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=2µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=2µs</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_connecting............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=2.68µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  min=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     med=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      max=8.36ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=0s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_duration..............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=20.81ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> min=1.48ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> med=20.5ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  max=237.88ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=28.33ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=32.2ms</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">expected_response:true</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> }...:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=20.81ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> min=1.48ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> med=20.5ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  max=237.88ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=28.33ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=32.2ms</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_failed................:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 0.00%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   ✓</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">           ✗</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 287124</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_receiving.............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=24.9µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  min=4µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    med=13µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    max=36.93ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=30µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=45µs</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_sending...............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=8.13µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  min=1µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    med=5µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     max=27.71ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=8µs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=11µs</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_tls_handshaking.......:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      min=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     med=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      max=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">       p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=0s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=0s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_req_waiting...............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=20.78ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> min=1.44ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> med=20.47ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> max=237.86ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=28.29ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=32.15ms</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     http_reqs......................:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 287124</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  4784.152692/s</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     iteration_duration.............:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avg=20.89ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> min=1.89ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> med=20.57ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> max=248.07ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=28.41ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">95</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">=32.28ms</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">     iterations.....................:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 287124</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  4784.152692/s</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br></div></div><h2 id="参考资料" tabindex="-1">参考资料 <a class="header-anchor" href="#参考资料" aria-label="Permalink to &quot;参考资料&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/" target="_blank" rel="noreferrer">https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/</a></li>
<li><a href="https://www.youtube.com/watch?v=HK04UxENH10" target="_blank" rel="noreferrer">https://www.youtube.com/watch?v=HK04UxENH10</a></li>
<li><a href="https://deno.com/blog/roll-your-own-javascript-runtime" target="_blank" rel="noreferrer">https://deno.com/blog/roll-your-own-javascript-runtime</a></li>
<li><a href="https://github.com/fermyon" target="_blank" rel="noreferrer">https://github.com/fermyon</a></li>
<li><a href="https://wasmer.io/" target="_blank" rel="noreferrer">https://wasmer.io/</a></li>
<li><a href="https://github.com/firecracker-microvm/firecracker" target="_blank" rel="noreferrer">https://github.com/firecracker-microvm/firecracker</a></li>
<li><a href="https://spectreattack.com/spectre.pdf" target="_blank" rel="noreferrer">https://spectreattack.com/spectre.pdf</a></li>
<li><a href="https://supabase.com/blog/edge-runtime-self-hosted-deno-functions" target="_blank" rel="noreferrer">https://supabase.com/blog/edge-runtime-self-hosted-deno-functions</a></li>
<li><a href="https://landscape.cncf.io/?group=serverless&amp;view-mode=grid" target="_blank" rel="noreferrer">https://landscape.cncf.io/?group=serverless&amp;view-mode=grid</a></li>
<li><a href="https://www.datadoghq.com/state-of-serverless/" target="_blank" rel="noreferrer">https://www.datadoghq.com/state-of-serverless/</a></li>
<li><a href="https://blog.cloudflare.com/workerd-open-source-workers-runtime/" target="_blank" rel="noreferrer">https://blog.cloudflare.com/workerd-open-source-workers-runtime/</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[我为什么选择创业]]></title>
            <link>https://code2life.top/blog/0100-startup</link>
            <guid>https://code2life.top/blog/0100-startup</guid>
            <pubDate>Thu, 02 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="我为什么选择创业" tabindex="-1">我为什么选择创业 <a class="header-anchor" href="#我为什么选择创业" aria-label="Permalink to &quot;我为什么选择创业&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="职业生涯的第一个十年" tabindex="-1">职业生涯的第一个十年 <a class="header-anchor" href="#职业生涯的第一个十年" aria-label="Permalink to &quot;职业生涯的第一个十年&quot;">&ZeroWidthSpace;</a></h2>
<p>在7年前加入ZOOM时，我的职业目标非常明确，就是成为一名优秀的软件架构师。</p>
<p>然而，这次工作转换似乎带来了不曾设想的运气。</p>
<p>运气之一是获得了一些原始期权，上市后解决了家庭柴米油盐的问题；运气之二是遇到一位优秀的领导，给予团队较高的自由度；运气之三是刚好在入职后在一个救火项目，获得了很快的成长和较高的可见性。成为架构师的职业目标，也在4年前提前完成了。</p>
<p>那之后呢？<strong>下一个5年、10年要成为什么</strong>？<strong>人生的意义是什么</strong>？</p>
<p>我非常认可薛定谔说的这句话，“Life feeds on negative entropy”，即<strong>生命的意义在于反抗熵增</strong>。</p>
<p>熙然，相比于在公司里按部就班解决工程问题，创业才有机会制造更大范围的“负熵”。也只有创业，才能更纯粹地将技术与商业结合，给世界带来一点改变，这是我的诗和远方。</p>
<p>于是，我这些年一直在向创业的方向沉淀，在公司尽可能去做从零到一有挑战的事情、去中科大读MBA、去随时思考商业机会。</p>
<p>这些年，我也在等一个机缘，如今，AI大潮势不可挡，我提醒自己是时候离开这个待了7年的平台了。</p>
<h2 id="等风来-不如追风去" tabindex="-1">等风来，不如追风去 <a class="header-anchor" href="#等风来-不如追风去" aria-label="Permalink to &quot;等风来，不如追风去&quot;">&ZeroWidthSpace;</a></h2>
<p>我自认为是一个很理性的人，风险厌恶程度高，从这个角度看我是不适合创业的。</p>
<p>然而，创业是一种病，只有甘于平凡才能治愈。</p>
<p>当集齐了人、方向、钱三要素时，我便下定决心全身心投入创业。</p>
<p>首先是人的因素，我的合伙人Andy是行动派，技术过硬，有持续创业的经历，性格也很适合创业，他说了句很经典的话：“要么纳斯达克敲钟，要么回家种地”。其他几位在初创期帮忙做事的小伙伴也很靠谱。</p>
<p>其次是创业方向，考虑到团队的极客基因和现有的积淀，我们选择了高技术壁垒的GPU虚拟化赛道，入局AI领域做卖铲人，有清晰的产品目标、明确的潜在客户，非常适合小团队轻资产创业。在花了两周时间详细调研了这个细分领域的<a href="https://code2life.top/blog/0084-why-tensor-fusion" target="_blank" rel="noreferrer">竞争格局</a>之后，更加确信这个方向的可行性。</p>
<p>最后是钱的因素，在当前AI飞速发展的大趋势下，选定AI Infra这个领域后，投资人很容易找，要考虑的是<strong>哪些钱不应该拿</strong>，如何找对的投资人。于是，我们在天使轮只拿了少量信任关系强的自然人投资，短期只做产品打磨、单个中大型企业的案例验证。</p>
<h2 id="开始行动" tabindex="-1">开始行动! <a class="header-anchor" href="#开始行动" aria-label="Permalink to &quot;开始行动!&quot;">&ZeroWidthSpace;</a></h2>
<p>集齐了人、方向、钱三要素后，便再也没理由回答“为什么不创业”了。先走出去，边走边看吧！就像罗振宇跨年演讲所说：</p>
<blockquote>
<p>一具体，就深刻；
一行动，就创新；
一困惑，就出门。</p>
</blockquote>
<p>最后，以我们一位投资人激励我的话结束：</p>
<blockquote>
<p>征途纵有棘荆拦，莫负初心志似磐。
且待云开霾雾散，遥看彼岸凯歌还。</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[2024年个人总结]]></title>
            <link>https://code2life.top/blog/0099-2024-summary</link>
            <guid>https://code2life.top/blog/0099-2024-summary</guid>
            <pubDate>Wed, 01 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="_2024年个人总结" tabindex="-1">2024年个人总结 <a class="header-anchor" href="#_2024年个人总结" aria-label="Permalink to &quot;2024年个人总结&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="工作" tabindex="-1">工作 <a class="header-anchor" href="#工作" aria-label="Permalink to &quot;工作&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="平台工程的下一站" tabindex="-1">平台工程的下一站 <a class="header-anchor" href="#平台工程的下一站" aria-label="Permalink to &quot;平台工程的下一站&quot;">&ZeroWidthSpace;</a></h3>
<p>2024年我的工作重心仍然在<strong>平台工程</strong>，为了解决公司基础设施的遗留问题，迭代了不少Kubernetes基建之外的功能，满足自建机房、非后端业务、复杂业务的各种长尾定制需求。</p>
<p>这一年功能迭代稍微能喘口气，解决一些稳定性和质量问题、服务运营体系搭建完善。从公司的满意度调查结果看，上千个内部的工程师用户给我们平台打了85分，喜忧参半。</p>
<p>印象比较深的里程碑有两个。一个是数千个域名换非通配TLS证书；另一个是作为FinOps小组关键成员之一，为公司节省了每月数百万美元成本，FinOps年后一定得总结输出一个系列文章。</p>
<p>这一年团队变化比较大，出差苏州两次，在管理方面遇到了一些挑战。从结果看，自己在管理方面还欠缺很多，具体细节不便展开。</p>
<h3 id="在哪里重生" tabindex="-1">在哪里重生 <a class="header-anchor" href="#在哪里重生" aria-label="Permalink to &quot;在哪里重生&quot;">&ZeroWidthSpace;</a></h3>
<p>除了本职工作，这一年一直在想办法打破个人和团队发展的天花板。</p>
<p>上半年做了一次尝试，失败了。</p>
<p>当时参加公司年度AI Hackathon活动，用一周时间做了个“AI端到端测试助手”的原型。</p>
<p>产品形态是VSCode插件，输入是PM的需求和前端代码仓库，从源码层面分析DOM selector，结合RAG和LLM Flow，来准确生成E2E自动化测试用例、用户手册文档。</p>
<p>这个想法和产品原型在几轮PK后进入了四强，最终没能获奖。</p>
<p>其实，系统学习了MBA后，我很清楚一家公司的战略地图如何制定，<strong>只有在战略地图上的事才可能被重视</strong>。</p>
<p>所以，我也坦然接受了结果，这件事做的再好，也不太可能在一个做协作办公产品的SaaS公司中孵化出来。而之后外面的事，就是Cursor、Devin、Codium、Qodo Gen、WindSurf这些产品爆火，AI编程成为24年下半年的投资风口。</p>
<p>目前为止，我仍然认为那个产品是能拿来创业的，E2E测试和用户文档是前端开发和Tech Writer的巨大痛点。现在市面上的AI编程助手、AI浏览器自动化产品都是“通才”，而这个场景太需要“AI专才”了，“通才”不能很好解决垂直领域的问题。</p>
<p>引用《长安的荔枝》中的话：<strong>就算失败，我也想知道，自己倒在距离终点多远的地方</strong>。</p>
<p>这件事只是个小插曲，10月份发生了一件意义重大的事情，让我再次找到了打破桎梏的希望，下一篇再说。</p>
<h2 id="学习" tabindex="-1">学习 <a class="header-anchor" href="#学习" aria-label="Permalink to &quot;学习&quot;">&ZeroWidthSpace;</a></h2>
<p>24年学习的开源项目多一些，Star过<strong>240</strong>多个开源项目，重点关注了一些AI项目，看到的精华在下一节整理回顾。</p>
<p>AI方面的学的越多，越感觉到<strong>人与人的关系正在被智能世界解耦</strong>，StackOverflow流量腰斩、各种交流社区正在没落。AI做的事情越多，人与人的交流越少、观点越割裂。</p>
<p>我相信硅基生命有一天会到来，作为碳基生命，能做的是多学习些开眼的新东西，多关注“下蛋的鸡”，看看每个项目背后是哪些人，了解他们有哪些精彩的故事。</p>
<h3 id="技术" tabindex="-1">技术 <a class="header-anchor" href="#技术" aria-label="Permalink to &quot;技术&quot;">&ZeroWidthSpace;</a></h3>
<p><strong>AI方向</strong></p>
<p>AI技术关注了三个方面：<strong>AI模型、AI开发框架、AI Infra</strong>，印象比较深刻的项目有：</p>
<ul>
<li>AI Agent开发框架<a href="https://github.com/microsoft/autogen" target="_blank" rel="noreferrer">AutoGen</a></li>
<li>LLM Flow Engineering项目<a href="https://github.com/FlowiseAI/Flowise" target="_blank" rel="noreferrer">Flowwise</a></li>
<li>AI数据摄取框架<a href="https://github.com/jina-ai/reader" target="_blank" rel="noreferrer">AI Reader</a></li>
<li>AI浏览器自动化<a href="https://github.com/browser-use/browser-use" target="_blank" rel="noreferrer">Browser Use</a></li>
<li>GPU虚拟化和池化。这是我最近很关注的领域，重点看了第四范式、Daocloud、华为合作开发的<a href="https://github.com/Project-HAMi/HAMi" target="_blank" rel="noreferrer">HAMi</a></li>
<li>AI训练、开发、数据处理、部署的全套框架<a href="https://github.com/ray-project/ray" target="_blank" rel="noreferrer">Project Ray</a>，感觉Ray背后的公司Anyscale，有望成为挑战HuggingFace地位的公司</li>
<li>DeepSeek大模型。我在OpenAI充的几百块钱用完后，朋友推荐这个模型效果不错，10月份左右换成了DeepSeek的API，充了10块钱竟然一直用到现在，背后扮猪吃老虎的公司幻方量化最近出圈了。我也在尝试能不能把AI编程环境也换成<a href="https://github.com/deepseek-ai/DeepSeek-Coder" target="_blank" rel="noreferrer">DeepSeek-Coder</a></li>
</ul>
<p><strong>前端方向</strong></p>
<p>24年我们团队内部跟随HeadlessUI的趋势，做了实践落地。前端生态对开发体验的极致追求，对关注点分离和函数式思想的执着，造就了“一切皆为Composable”。另一个趋势TailwindCSS的广泛普及，已经成为事实标准。至此，HTML-JS-CSS三要素，可以全部用函数组合解决。</p>
<ul>
<li>组合React Hooks/Vue Composable实现业务逻辑；</li>
<li>组合React/Vue Component或WebComponents实现页面结构；</li>
<li>组合Tailwind原子CSS和封装后的CSS Class实现交互视效。</li>
</ul>
<p>以下是个人关注的一些有趣的前端项目：</p>
<ul>
<li>新兴的UI框架，比如<a href="https://github.com/primefaces/primevue" target="_blank" rel="noreferrer">PrimeVue</a>、<a href="https://daisyui.com/" target="_blank" rel="noreferrer">DaisyUI</a>、大多数海外创业项目，都在用TailwindCSS</li>
<li>D3创始人开发的新一代的数据可视化框架<a href="https://github.com/f5/unovis" target="_blank" rel="noreferrer">Unovis</a></li>
<li>基于CheerpX把整个操作系统编译成WASM，在浏览器运行Linux的<a href="https://github.com/leaningtech/webvm" target="_blank" rel="noreferrer">WebVM</a>，甚至通过TailScale支持了完整的TCP/IP网络协议栈</li>
<li>把整个Container编译成WASM的<a href="https://github.com/ktock/container2wasm" target="_blank" rel="noreferrer">Container2WASM</a></li>
<li>在浏览器运行GPU大模型推理的<a href="https://github.com/mlc-ai/web-llm" target="_blank" rel="noreferrer">WebLLM</a>，这个项目背后是WebGPU标准以及对应的实现<a href="https://github.com/gfx-rs/wgpu" target="_blank" rel="noreferrer">WGPU</a>、<a href="https://github.com/google/dawn" target="_blank" rel="noreferrer">Dawn</a></li>
<li>Terminal UI(TUI)框架：<a href="https://github.com/vadimdemedes/ink" target="_blank" rel="noreferrer">ink</a>、<a href="https://github.com/charmbracelet/bubbletea" target="_blank" rel="noreferrer">ButtleTea</a></li>
</ul>
<p><strong>后端和基础软件方向</strong></p>
<ul>
<li>Postgres数据库封神，<a href="https://survey.stackoverflow.co/2024/technology#most-popular-technologies-database-prof" target="_blank" rel="noreferrer">在24年流行度首次超过MySQL</a>。PG 17的发布、AI应用的爆发，Make Postgres Great Again。个人Star过一些PG生态项目有：<a href="https://github.com/tensorchord/pgvecto.rs" target="_blank" rel="noreferrer">PGVector.RS</a>、<a href="https://github.com/dolthub/doltgresql" target="_blank" rel="noreferrer">Doltgresql</a>、<a href="https://github.com/citusdata/citus" target="_blank" rel="noreferrer">Azure Cosmos DB for PostgreSQL底层的Citus</a></li>
<li>Kubernetes集群的节点组池化方案<a href="https://karpenter.sh/" target="_blank" rel="noreferrer">Karpenter</a>也成为了事实标准，甚至出现了基于Multi-cloud Karpenter的FinOps创业公司<a href="https://github.com/cloudpilot-ai" target="_blank" rel="noreferrer">CloudPilot AI</a></li>
<li>大型云厂跟Redis关系破裂，Redis社区分裂出<a href="https://github.com/valkey-io/valkey" target="_blank" rel="noreferrer">Valkey</a></li>
<li>密钥管理平台<a href="https://github.com/Infisical/infisical" target="_blank" rel="noreferrer">infisical</a>，这个项目印象深刻，是因为我参与过公司密钥平台的构建，理解为什么这个项目能火起来。像Secret Manager、Vault这些引擎层产品最大的问题是功能太单薄，满足不了企业级的密钥治理需求，比如元数据管理、密钥轮转、热加载等等。</li>
<li>授权决策框架<a href="https://github.com/openfga/openfga" target="_blank" rel="noreferrer">OpenFGA</a>，AuthZ框架非常多，我看过、用过很多，看起来很简单，做对很难。读了OpenFGA源码和文档后，感觉这个项目鹤立鸡群，甚至忍不住贡献了一些代码。年后再单独写一篇文章讲讲：为什么Google Zanzibar提出的ReBAC是更适合B2B场景的AuthZ选型。</li>
<li>新兴的工作流编排产品<a href="https://github.com/kestra-io/kestra" target="_blank" rel="noreferrer">Kestra</a>，产品设计挺好。</li>
<li>Kubernetes智能看板<a href="https://github.com/KusionStack/karpor" target="_blank" rel="noreferrer">Karpor</a>，KusionStack体系的新项目，用ElasticSearch转SQL的方式，把Kubernetes集群资源做成数仓，很有意思，解决了多集群Kubernetes数据集中管理问题。</li>
<li>工作流引擎<a href="https://github.com/restatedev/restate" target="_blank" rel="noreferrer">Restate</a>。23年我重点推荐过<a href="https://code2life.top/blog/0070-temporal-notes" target="_blank" rel="noreferrer">Temporal</a>，24年Rust生态出现了Temporal的挑战者<a href="https://github.com/restatedev/restate" target="_blank" rel="noreferrer">Restate</a>，果然，<strong>一切可能被Rust重写的都会被Rust重写</strong>。</li>
<li>GPU Database <a href="https://github.com/heavyai/heavydb" target="_blank" rel="noreferrer">HeavyDB</a>。GPU全称是GPGPU，既然General Purpose GPU，为何不能做数据库？超高内存带宽、超强并行计算能力、跳过CPU直接Zero Copy写入NVMe的能力，刚好符合了大规模的数据读写和分析场景，虽然这个GPU数据库产品离普及还很远，这个方向是值得关注的。</li>
<li>Notification as code项目<a href="https://github.com/novuhq/novu" target="_blank" rel="noreferrer">novu</a>。给用户发送通知一直是Web开发最基础的能力之一，这个项目简化了给用户编排复杂通知业务的开发成本。</li>
<li>Everything as code项目<a href="https://github.com/pulumi/pulumi" target="_blank" rel="noreferrer">Pulumi</a>。IaC/EaC是运维自动化的基础，我3年前开始关注Pulimi这家做IaC的初创公司，工作中也参考了一些Pulumi的设计。Pulumi在23年底完成了$4100万C轮融资，证明了这是个好产品，也是Terraform最大的挑战者。我自己也在用Pulumi，TUI交互很不错，GUI差强人意。</li>
<li>新兴云原生时序数据库<a href="https://github.com/GreptimeTeam/greptimedb" target="_blank" rel="noreferrer">GreptimeDB</a>。年底刚发现的一个Rust生态的好项目，解决了监控场景几个关键痛点：Prometheus单值体系无法大规模Scale和持久化、Clickhouse运维和写SQL复杂、监控日志数据库用块存储成本高昂、监控系统自监控问题、持续聚合和长期指标存储能力等等。这家公司创立刚两年，产品功能已经相当完善了，值得去试一试。</li>
</ul>
<p><img src="https://filecdn.code2life.top/pg-ecosystem.jpg" alt=""></p>
<p><strong>SaaS产品</strong></p>
<p>分享一些24年试用过、或正在使用的SaaS产品：</p>
<ul>
<li><strong>Teable</strong>：开源的Airtable替代品。</li>
<li><strong>Webflow</strong>：建站首选Webflow，没有找到第二家做到这个程度的，试用下就不用解释了。</li>
<li><strong>Incident.io</strong>：做Status Page的新产品，对比了一下BetterStack，最终还是选择了Incident.io</li>
<li><strong>Harness Cloud Costs Mgmt.</strong>：Harness是持续交付领域的独角兽公司了，关(chao)注(xi)了3年多，24年做FinOps项目，发现Harness竟然也做了云成本管控模块，体验下来感觉很不错，继续关(chao)注(xi)。</li>
<li><strong>Cloudflare</strong>：有理想、有追求、不作恶的云厂商，24年发布了用Worker体系运行Container的能力。我的个人网站、创业项目已经All in Cloudflare了。</li>
<li><strong>Resend</strong>：像写React一样写用户邮件通知</li>
<li><strong>WindSurf / Codium / Cursor</strong>：24年是AI编程爆发的一年，个人感觉试用效果不错的是WindSurf，唯一的缺点是我习惯用CMD + I快捷键做类型提示的代码补全，但快捷键被AI助手覆盖了要自己改键，这点很不舒服。</li>
<li><strong>Clerk</strong>：SaaS化的用户系统，配合Supabase可以快速开发一个B2B业务</li>
<li><strong>Lago</strong>：开源的Subscription Plan管理系统</li>
</ul>
<p>在技术方面，今年还做了一件小事。把个人站点用Vitepress重构了一遍，加上Giscus评论和RSS，去掉了粒子特效，背景换成了泛黄的纸张纹理。在30岁生日，送自己一个极简风格的数字花园。</p>
<h3 id="分享" tabindex="-1">分享 <a class="header-anchor" href="#分享" aria-label="Permalink to &quot;分享&quot;">&ZeroWidthSpace;</a></h3>
<p>团队内部的知识分享活动，我自己讲了2次。一次讲技术，主题是<strong>如何执行用户的非可信代码</strong>，另一次讲财务，主题是<strong>如何解读财报</strong>。除外，还有一次小范围讲OpenFGA设计原理和使用方式。这些知识分享资料，年后也会把跟公司业务无关的部分逐步整理发出来。</p>
<p>这年的文章输出仍然求精不求多，每篇都在5000字以上：</p>
<ul>
<li><a href="https://code2life.top/blog/0078-how-to-write-good-tech-docs" target="_blank" rel="noreferrer">如何写出高质量的技术文档</a></li>
<li><a href="https://code2life.top/blog/0079-what-is-saas" target="_blank" rel="noreferrer">SaaS的本质是什么</a></li>
<li><a href="https://code2life.top/blog/0084-why-tensor-fusion" target="_blank" rel="noreferrer">为什么Tensor Fusion能够颠覆GPU虚拟化</a></li>
</ul>
<h3 id="读书" tabindex="-1">读书 <a class="header-anchor" href="#读书" aria-label="Permalink to &quot;读书&quot;">&ZeroWidthSpace;</a></h3>
<p>读书和分享集中在中间两个季度，读的比较少，只有13本书，有闲暇就看下，但没坚持太久。</p>
<p><img src="https://m.media-amazon.com/images/I/714hhkkcbCL.jpg" alt=""></p>
<p><strong>管理学相关的书</strong>：</p>
<ul>
<li>2021版的《助推》，原书名《Nudge》，作者 Richard H. Thaler, Cass R. Sunstein</li>
<li>《HR+三支柱——人力资源管理转型升级与创新实践》—— 马海刚 彭剑锋 西楠</li>
<li>《从总账到总监：CFO的独家财务笔记》—— 钱自严</li>
<li>《从报表看企业——数字背后的秘密》——张新民</li>
</ul>
<p><strong>不带目的，随便看的小说</strong>：</p>
<ul>
<li>《鲸歌》—— 刘慈欣</li>
<li>《球状闪电》—— 刘慈欣</li>
<li>《太白金星有点烦》—— 马伯庸</li>
</ul>
<p><strong>其他类的书</strong>：</p>
<ul>
<li>《金刚经说什么》—— 南怀瑾</li>
<li>朋友推荐的《爱的五种语言》 —— Gary Chapman</li>
<li>微信读书算法推荐的《法律的悖论》、《毛泽东选集》、《党委会的工作方法》</li>
<li>老婆推荐我看的《在峡江的转弯处：陈行甲人生笔记》</li>
</ul>
<p>最后，重磅推荐罗振宇的《文明之旅》节目，一集没落听到现在，每集都会有不同的启发，发愿做20年节目，每集高质量输出一个多小时，真是个狠人。</p>
<h2 id="生活" tabindex="-1">生活 <a class="header-anchor" href="#生活" aria-label="Permalink to &quot;生活&quot;">&ZeroWidthSpace;</a></h2>
<p>生活方面，24年下半年遇到了一个挺大的挫折，调整了一段时间，全年总体还不错。</p>
<p>养了两只鹦鹉，添了两颗柠檬树。</p>
<p>这一年家庭旅行，去的都是比较近的地方：</p>
<ul>
<li>江西景德镇，体验陶艺，</li>
<li>铜陵犁桥水镇，看打铁花，</li>
<li>芜湖松鼠小镇，一点点小刺激，</li>
<li>庐江的民宿和农场，休闲小憩，</li>
<li>安徽博物院、地质博物馆、园博园昆虫博物馆，涨涨知识，</li>
<li>金寨马鬃岭，看三花槭染红整片森林</li>
<li>南京红山动物园、合肥野生动物园，满足孩子对动物的执念。</li>
</ul>
<p>旅行总是匆匆瞥过，而身边的美，要特写镜头来感受。</p>
<p>收获最多欢笑的地方，是不到500米的公园。和老婆孩子走到湖边，攥一个木棍，扔几块石头，看野鸭子游泳。</p>
<p>公园里落羽杉、四叶草、金鸡菊、蒲公英，或星点散落，或连成一片，不同季节换上不同的模样。</p>
<p>未霜乌桕赤，另日海棠红。</p>
<p><strong>最美的风景来自内心，来自身边的人</strong>。</p>
<p><img src="https://filecdn.code2life.top/-wHXwnGewfjoyVrcTmDYC_b2bd6a584e4f4e51b8d381a69feeeaa1.jpg" alt=""></p>
<h2 id="总结和展望" tabindex="-1">总结和展望 <a class="header-anchor" href="#总结和展望" aria-label="Permalink to &quot;总结和展望&quot;">&ZeroWidthSpace;</a></h2>
<p>去年给今年定的关键词是“行动、行动、行动”，一年下来，做了一些有意义的事情，但一些没有克服掉的拖、懒、贪，导致不少时间白白浪费了，综合来看，勉强能给自己一个交代吧。</p>
<p>新的一年，拒绝拖延，从按时写年终总结开始。</p>
<p>最后借用罗振宇跨年演讲的一句话：<strong>所有来自未来的好消息，都是我们今天努力的模样</strong>。</p>
<p>2025年的关键词，继续用这六个字吧：
<strong>行动、行动、行动!</strong></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Why Tensor Fusion is the Game Changer in GPU Virtualization]]></title>
            <link>https://code2life.top/blog/0084-why-tensor-fusion-en</link>
            <guid>https://code2life.top/blog/0084-why-tensor-fusion-en</guid>
            <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="why-tensor-fusion-is-the-game-changer-in-gpu-virtualization" tabindex="-1">Why Tensor Fusion is the Game Changer in GPU Virtualization <a class="header-anchor" href="#why-tensor-fusion-is-the-game-changer-in-gpu-virtualization" aria-label="Permalink to &quot;Why Tensor Fusion is the Game Changer in GPU Virtualization&quot;">&ZeroWidthSpace;</a></h1>
<nav class="table-of-contents"><ul><li><a href="#background">Background</a></li><li><a href="#why-gpu-virtualization-is-needed">Why GPU Virtualization is Needed</a></li><li><a href="#_4-gpu-virtualization-approaches">4 GPU Virtualization Approaches</a><ul><li><a href="#hardware-and-driver-layer-sharing-mechanisms">Hardware and driver layer sharing mechanisms</a></li><li><a href="#para-virtualization-with-virtual-devices">Para-virtualization with Virtual Devices</a></li><li><a href="#co-located-task-scheduling-emulation">Co-located task scheduling emulation</a></li><li><a href="#api-remoting-compute-virtualization">API Remoting Compute Virtualization</a></li></ul></li><li><a href="#where-are-tensor-fusion-s-opportunities">Where are Tensor Fusion&#39;s opportunities?</a><ul><li><a href="#market">Market</a></li><li><a href="#product">Product</a></li><li><a href="#team">Team</a></li><li><a href="#technology">Technology</a></li></ul></li><li><a href="#conclusion">Conclusion</a></li><li><a href="#reference">Reference</a></li></ul></nav>
<h2 id="background" tabindex="-1">Background <a class="header-anchor" href="#background" aria-label="Permalink to &quot;Background&quot;">&ZeroWidthSpace;</a></h2>
<p>Recently, while working on our company's cloud cost optimization, most cloud resource cost control strategies were clear, but the exorbitant GPU fees remained a headache.</p>
<p>In October, I caught up with an old friend who happened to be researching GPU virtualization. After seeing his prototype, my intuition told me it will be a <strong>revolutionary technology</strong>, possibly it could create a <strong>unicorn-level company</strong>.</p>
<p>We immediately aligned our vision and started working on the project in our spare time, naming it <a href="https://tensor-fusion.ai/" target="_blank" rel="noreferrer">Tensor Fusion</a>.</p>
<p>As our research into GPU virtualization deepened, I compiled some valuable insights that both answer <strong>why we're committed to this venture</strong> and demonstrate to users or investors <strong>where our product value lies</strong>.</p>
<p>Before diving into the technical discussion, you can check out the Demo to understand what Tensor Fusion is. The docs are available here: <a href="https://tensor-fusion.ai/guide/get-started" target="_blank" rel="noreferrer">Get Started</a>. We welcome trials and feedback.</p>
<video-player src="https://filecdn.code2life.top/TensorFusion-demo.mp4" poster="https://filecdn.code2life.top/tfs-no-play-poster.png" /><p><strong>After reading this article, you'll have answers to these questions:</strong></p>
<ul>
<li>Why is GPU virtualization necessary?</li>
<li>What are the technical approaches to GPU virtualization, and what are their core principles?</li>
<li>What are the pros and cons of these GPU virtualization technologies?</li>
<li>Why hasn't the industry produced a perfect GPU virtualization and pooling solution yet?</li>
<li>What gives us the confidence to say we can revolutionize this field?</li>
</ul>
<h2 id="why-gpu-virtualization-is-needed" tabindex="-1">Why GPU Virtualization is Needed <a class="header-anchor" href="#why-gpu-virtualization-is-needed" aria-label="Permalink to &quot;Why GPU Virtualization is Needed&quot;">&ZeroWidthSpace;</a></h2>
<p>While investigating GPU cost issues, I noticed that each service instance exclusively occupied a GPU. Although individual GPU utilization could reach 70% during peak business hours, the <strong>overall GPU cluster utilization never exceeded 20%</strong>.</p>
<p>This example perfectly illustrates why GPU virtualization is necessary. Without virtualization, there's no way to <strong>safely share GPUs</strong>, meaning <strong>80% of resources are wasted, paying cloud providers 400% more than necessary</strong>!</p>
<p><strong>In professional terms, GPU virtualization serves these purposes:</strong></p>
<ol>
<li>Virtualization is a means to <strong>share</strong> underlying physical resources, avoiding waste of expensive GPU resources</li>
<li>Virtualization can isolate failures, memory addresses, and control quotas, which is <strong>prerequisite for secure multi-tenancy</strong></li>
<li>Virtualization is the <strong>foundation for elastic scaling</strong>, enabling <strong>reduced tail latency and increased throughput</strong> during high concurrency.</li>
</ol>
<p>IaaS has been developing for years, and CPU virtualization is almost perfect, but GPU is still used in mounting physical devices way, <strong>which is obviously unreasonable</strong>.</p>
<p>Can GPU virtualization be achieved? Yes, there are several solutions in the industry, and what are the problems?</p>
<h2 id="_4-gpu-virtualization-approaches" tabindex="-1">4 GPU Virtualization Approaches <a class="header-anchor" href="#_4-gpu-virtualization-approaches" aria-label="Permalink to &quot;4 GPU Virtualization Approaches&quot;">&ZeroWidthSpace;</a></h2>
<p>Currently, there are 4 solutions in the industry, and we'll discuss them <strong>from low to high</strong> in terms of abstraction:</p>
<ol>
<li>Hardware and driver layer built-in sharing mechanisms, <strong>strictly speaking, not virtualization</strong></li>
<li>Virtual devices implemented <strong>para-virtualization</strong></li>
<li><strong>Fake virtualization</strong> based-on co-located task scheduling</li>
<li><strong>GPU Computing Power virtualization</strong> based on API forwarding</li>
</ol>
<h3 id="hardware-and-driver-layer-sharing-mechanisms" tabindex="-1">Hardware and driver layer sharing mechanisms <a class="header-anchor" href="#hardware-and-driver-layer-sharing-mechanisms" aria-label="Permalink to &quot;Hardware and driver layer sharing mechanisms&quot;">&ZeroWidthSpace;</a></h3>
<p>GPU hardware and driver layer has built-in multi-user isolation and sharing mechanisms, and each GPU manufacturer has its own implementation.</p>
<p>For example, NVIDIA's built-in sharing mechanisms mainly include 3 types:</p>
<ol>
<li><a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/" target="_blank" rel="noreferrer">Multi-instance GPU</a> (MIG) ，<strong>Multi-instance GPU</strong>. Similar to cutting cake, dividing the GPU into several completely isolated parts, but only 7 sub-GPUs can be produced at most, and the memory isolation is GB-level，only supported by <strong>Ampere architecture</strong> after 2020.</li>
<li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html#comparison-time-slicing-and-multi-instance-gpu" target="_blank" rel="noreferrer">Time-slicing</a> is the popular oversubscription way in Kubernetes. It lacks memory and fault isolation. NVIDIA just <strong>presents a single GPU as multiple devices</strong> to different Pods. There's no control over Pods launching multiple processes to compete for time slices. Think of it like a <strong>buffet line with no portion control</strong> - nothing stops one person from taking everything.</li>
<li><a href="https://docs.nvidia.com/deploy/mps/index.html" target="_blank" rel="noreferrer">Multi-process Service</a> (MPS) is a variant of Time-slicing that shares CUDA Context across multiple processes. This allows the MPS scheduler to insert tasks whenever CUDA cores are idle, rather than relying on <strong>concurrent execution and CUDA context switching</strong> like Time-slicing does. Prior to 2017, NVIDIA provided MPS scheduling through a software-layer mps-server. With the introduction of Volta architecture GPUs, MPS expanded to support up to 48 simultaneous processes and added memory address space isolation, though it still lacks OOM protection and fault isolation. In most cases, <a href="https://github.com/pytorch/serve/blob/master/docs/nvidia_mps.md" target="_blank" rel="noreferrer">MPS achieves higher efficiency than Time-slicing</a>. Think of MPS like a <strong>restaurant wait list system</strong> - as soon as a table opens up, the next customer can be seated.</li>
</ol>
<p>To sum up, <strong>MIG is space division multiplexing (Space division multiplexing), Time-slicing and MPS are time division multiplexing (Time division multiplexing)</strong>, the detailed comparison can refer to <a href="https://github.com/rh-aiservices-bu/gpu-partitioning-guide" target="_blank" rel="noreferrer">this document</a>.</p>
<p><img src="https://filecdn.code2life.top/nvidia-mig.png" alt=""></p>
<p>Combing MIG and Time-slicing/MPS can achieve a similar effect to GPU virtualization, but the granularity is too coarse to fundamentally improve GPU utilization, and none of them can achieve <strong>VRAM oversubscription</strong>, if Time-slicing is used, it will also bring <strong>reduced availability and increased latency</strong> risks.</p>
<p>In real cases, although this solution cannot achieve <strong>fine-grained resource control</strong> over GPUs, it is simple and easy to implement, and can meet the most basic business needs when combined with Kubernetes cluster's pooled scheduling capabilities.</p>
<p>There is an open-source project <a href="https://nebuly-ai.github.io/nos/dynamic-gpu-partitioning/partitioning-modes-comparison" target="_blank" rel="noreferrer">Nebuly NOS</a> based on NVIDIA's MIG+MPS to <strong>dynamically split GPU devices</strong> in Kubernetes, which is more automated and has better scheduling effects than NVIDIA's native Kubernetes solution.</p>
<h3 id="para-virtualization-with-virtual-devices" tabindex="-1">Para-virtualization with Virtual Devices <a class="header-anchor" href="#para-virtualization-with-virtual-devices" aria-label="Permalink to &quot;Para-virtualization with Virtual Devices&quot;">&ZeroWidthSpace;</a></h3>
<h4 id="_1-what-are-virtual-devices" tabindex="-1">1. What are Virtual Devices? <a class="header-anchor" href="#_1-what-are-virtual-devices" aria-label="Permalink to &quot;1. What are Virtual Devices?&quot;">&ZeroWidthSpace;</a></h4>
<p>Virtual I/O devices have evolved for years in IaaS - this is the &quot;orthodox approach&quot; to GPU virtualization.</p>
<p>When implementing virtual devices, pure software emulation (full virtualization) is typically avoided to minimize performance impact. Instead, <strong>para-virtualization</strong> is used to balance performance and security.</p>
<p>The basic idea is: isolating the dangerous <strong>device control layer</strong> in the hypervisor while passing through the <strong>device data and functional layers</strong> directly to VMs.</p>
<p>Virtual devices are technically complex, involving things like IOMMU for memory page table isolation and driver function pointer isolation, which we won't detail here.</p>
<p><img src="https:///filecdn.code2life.top/virtual-gpu-technology-vgpu-11-software-stack.jpg" alt=""></p>
<h4 id="_2-three-variants-of-virtual-device-implementation" tabindex="-1">2. Three Variants of Virtual Device Implementation <a class="header-anchor" href="#_2-three-variants-of-virtual-device-implementation" aria-label="Permalink to &quot;2. Three Variants of Virtual Device Implementation&quot;">&ZeroWidthSpace;</a></h4>
<p>There are 3 variants for implementing GPU virtual devices: VFIO + SR-IOV, GRID vGPU, and VirtIO.</p>
<ol>
<li><strong>VFIO + SR-IOV</strong>: Think of VFIO as &quot;VF&quot; (Virtual Function) + &quot;IO&quot;. VMs call VFs which map to PFs (Physical Functions) on the device. SR-IOV is a PCIe device virtualization standard - when hardware vendors support it, hypervisors can manage device's logical replications accordingly. AMD mainly uses VFIO+SR-IOV for GPU virtualization, with performance loss under 4%.</li>
<li><strong>GRID vGPU</strong>: This is NVIDIA's proprietary commercial virtual device solution. NVIDIA developed GRID vGPU early on, keeping it closed-source with expensive licensing, and some cloud vendors develop their own NVIDIA GPU virtualization. It uses Mediated Device (mdev) and modified drivers to achieve similar effects to VFIO + SR-IOV. As the world's highest-valued company, NVIDIA sets its own standards. Before that, Intel's GVT used a similar approach.</li>
<li><strong>VirtIO</strong>: Predating SR-IOV, VirtIO injects a &quot;fake driver&quot; into VMs. The hypervisor reads I/O requests from host-guest shared memory and forwards them to the &quot;real driver&quot; on the host. It introduces &quot;frontend&quot; and &quot;backend&quot; driver concepts, with only the flexible frontend visible in VMs. Performance loss is slightly higher but offers more flexibility. The <a href="https://github.com/coldfunction/qCUDA" target="_blank" rel="noreferrer">qCUDA</a> project demonstrates this approach.</li>
</ol>
<p>In practice, cloud vendors selling GPU VMs need traditional virtual device solutions, leading to proprietary implementations like <a href="https://www.alibabacloud.com/help/en/egs/what-is-cgpu" target="_blank" rel="noreferrer">Ali Cloud cGPU</a>.</p>
<h4 id="_3-why-virtual-devices-aren-t-the-ultimate-solution" tabindex="-1">3. Why Virtual Devices Aren't the Ultimate Solution <a class="header-anchor" href="#_3-why-virtual-devices-aren-t-the-ultimate-solution" aria-label="Permalink to &quot;3. Why Virtual Devices Aren't the Ultimate Solution&quot;">&ZeroWidthSpace;</a></h4>
<p>While virtual devices look promising with MB-level memory control and 1%/10% compute control per GPU, running in OS kernel space with proven security technologies, they aren't the complete answer.</p>
<p>Is virtual device the perfect state of GPU virtualization?</p>
<p>Of course not.</p>
<p>Just ask one question: <strong>What does users want?</strong></p>
<p>Does users need a VM with a GPU? Is it the 6912 CUDA cores and 4200MB VRAM on the GPU?</p>
<p>No, they don't.</p>
<p>Think from the <strong>first principles</strong>, users needs: <strong>to train/infer various neural network models to achieve business goals</strong>. So, there needs to be something that helps them <strong>perform tensor calculations at a rate of trillions of floating-point operations per second</strong>.</p>
<p>Is there a way to <strong>handle multiple tenants' requests as quickly, efficiently, and securely as possible</strong> in a limited GPU resource pool?</p>
<p>Thinking to this dimension, the virtualization of GPU devices is no longer important, <strong>offering TFLOPS, isolating and sharing computing power -- that is the true virtualization</strong> that meets user needs.</p>
<p>From the <strong>business</strong> perspective, what are the limitations of the virtual device approach?</p>
<ol>
<li>The <strong>resource quota of virtual devices is fixed</strong> and cannot be adjusted dynamically according to <strong>business</strong> peaks and troughs, thus the <strong>overall resource utilization is still not as high as expected</strong></li>
<li>CUDA cores can be oversubscribed, but generally cannot <strong>oversubscribe VRAM</strong>, but VRAM is likely to be the bottleneck, <strong>deploy much more AI apps for the business</strong> is not possible</li>
<li>Physical GPU devices must be mounted on the host, and drivers must be mounted on the <strong>business</strong> running environment, affecting elasticity and increasing management complexity</li>
<li>It is very hard to jointly accelerate a set of computing tasks across multiple GPUs on different machines, so <strong>business</strong> latency cannot be reduced</li>
<li>Too dependent on hardware vendors, it is impossible to build a heterogeneous cluster with multiple GPU vendors, <strong>business</strong> is still easily locked by GPU vendors</li>
</ol>
<h3 id="co-located-task-scheduling-emulation" tabindex="-1">Co-located task scheduling emulation <a class="header-anchor" href="#co-located-task-scheduling-emulation" aria-label="Permalink to &quot;Co-located task scheduling emulation&quot;">&ZeroWidthSpace;</a></h3>
<p>Among those limitations of virtual devices, the first one is the most serious: <strong>When business has obvious peaks and troughs, GPU utilization still not improved</strong>.</p>
<p>Therefore, there is a third type of solution emerged. The key innovation is: <strong>Drilling down scheduling from a coarse device level to a fine computing task level</strong>.</p>
<p>This approach adds a &quot;broker&quot; layer above the GPU devices. Users just tell the broker what target GPU utilization they need.</p>
<p>It's similar to hiring a contractor instead of individual workers. The contractor can efficiently manage multiple projects by assigning tasks to workers as needed, which works better than clients trying to manage workers directly.</p>
<p>How is this implemented? The system allows multiple applications to concurrently execute compute tasks on one GPU, called &quot;<strong>co-location tasks</strong>&quot;. Applications call user-space compute libraries like NVIDIA's libcuda and AMD's HIP SDK. By <strong>intercepting at this layer and adding a rate limiter</strong>, they can precisely control how these co-located tasks flow into physical devices and manage resource quotas.</p>
<p>This interception is typically done using <strong>LD_LIBRARY_PATH / LD_PRELOAD</strong> in user space, resulting in minimal performance overhead. The rate limiter usually uses a token bucket algorithm and polls GPU metrics via the nvml library.</p>
<p>Above the scheduler, compute quota interfaces are exposed to users through Kubernetes Device Plugins. Users can specify requests/limits like &quot;nvidia.com/vgpu: 1%&quot; in their resource specifications. Combined with native or custom Kubernetes schedulers, this enables cluster-level pooled compute allocation.</p>
<p>While the result  resembles virtual devices, it lacks memory address isolation and fault isolation, so it's not strictly virtualization. We'll call it &quot;co-location task scheduling emulation&quot;.</p>
<p>Here are some notable implementations from academia and industry:</p>
<ul>
<li><strong><a href="https://github.com/tkestack/vcuda-controller" target="_blank" rel="noreferrer">Gaia GPU</a></strong>: Implementation from <a href="https://ieeexplore.ieee.org/document/8672318" target="_blank" rel="noreferrer">a paper in 2018 by Tencent and Peking University paper</a> with 43 citations. Most similar research since then has built upon GaiaGPU's foundation</li>
<li><strong><a href="https://github.com/NTHU-LSALAB/KubeShare" target="_blank" rel="noreferrer">KubeShare</a> &amp; <a href="https://github.com/NTHU-LSALAB/Gemini" target="_blank" rel="noreferrer">Kernel Burst</a></strong>: Introduced kernel burst concepts and task execution prediction to improve scheduling efficiency, enabling Auto Scale on single GPUs</li>
<li><strong><a href="https://link.springer.com/article/10.1007/s42514-023-00154-y" target="_blank" rel="noreferrer">Ark GPU</a></strong>: Added load prediction models and QoS differentiation between LC (Latency-Critical) and BE (Best-Effort) workloads</li>
<li><strong><a href="https://github.com/Project-HAMi/HAMi" target="_blank" rel="noreferrer">Project HAMI</a></strong>: A CNCF Sandbox project (formerly k8s-vGPU-scheduler) focused on production deployment across multiple cloud providers. Core interception code in <a href="https://github.com/Project-HAMi/HAMi-core/blob/main/src/cuda/hook.c" target="_blank" rel="noreferrer">HAMi-core</a> is nearly identical to GaiaGPU</li>
<li><a href="https://run.ai" target="_blank" rel="noreferrer"><strong>RUN AI</strong></a>: An Israeli startup that has raised $118M, likely building on GaiaGPU while adding enterprise features like dynamic scheduling and GPU cluster management console</li>
</ul>
<p><img src="https://filecdn.code2life.top/gaia-gpu.png" alt=""></p>
<p>An interesting project is <strong>HuggingFace <a href="https://huggingface.co/docs/hub/spaces-zerogpu" target="_blank" rel="noreferrer">ZeroGPU</a></strong>, where CEO Clem Delangue invested $10M to build a free A100 inference cluster for AI developers. The key code in Gradio SDK hooks PyTorch APIs rather than NVIDIA Driver APIs. It uses the @<strong>space.GPU decorator</strong> to intercept Python inference functions, letting the scheduler manage GPU quotas. When resources are available, it executes on local 8xA100s and swaps memory to NVMe when functions cool down.</p>
<p><img src="https://filecdn.code2life.top/zero-gpu.png" alt=""></p>
<p>While ZeroGPU's high-level API hooking enables GPU time-sharing and QoS, it can't finely control VRAM per application - each app can use up to 40G VRAM of A100.</p>
<p>In summary, this third approach of co-located task scheduling virtualizes at the compute power level. By adding rate limiters to AI compute libraries and leveraging Kubernetes' native pool scheduling, it achieves relatively flexible resource control and multi-tenant sharing.</p>
<p><strong>But this is not enough. Why?</strong></p>
<p>Besides not solving problems #2/3/4/5 of the virtual device approach mentioned above, looking at the entire GPU pool, there are several unsolved challenges about multi-tenancy and pooling:</p>
<ul>
<li>Still bound to GPU devices - CPU and GPU scheduling remain coupled, making independent scaling impossible. Can't achieve GPU Scale to Zero with sub-second warm-up.</li>
<li>From a cluster perspective, while Kubernetes enables some pooling through distributed scheduling, there's no active defragmentation or GPU pool auto-scaling. Scheduling efficiency can't reach the next level and operational costs remain high (only run.ai among mentioned solutions does active scheduling and defragmentation)</li>
<li>No fault isolation or memory address isolation, making it unsafe for untrusted multi-tenant sharing.</li>
</ul>
<p>Can we think one step further along this path to thoroughly solve these issues?</p>
<h3 id="api-remoting-compute-virtualization" tabindex="-1">API Remoting Compute Virtualization <a class="header-anchor" href="#api-remoting-compute-virtualization" aria-label="Permalink to &quot;API Remoting Compute Virtualization&quot;">&ZeroWidthSpace;</a></h3>
<p>Let's return to first principles and think deeper to find the fundamental solution.</p>
<p>To achieve <strong>ultimate GPU compute sharing</strong>, we need to <strong>consolidate all compute power into one large pool</strong> for <strong>fine-grained scheduling across the entire pool</strong>.</p>
<p>It's like solving storage efficiency at the IaaS layer, where distributed NFS/Object Storage has become the de facto standard. NFS turns disks into remote storage services, enabling compute-storage separation.</p>
<p>Similarly, by turning <strong>GPUs into remote compute services</strong> and implementing <strong>GPU-CPU separation</strong>, we can create an architecture with <strong>compute fusion + fine-grained scheduling control</strong> - <strong>using GPUs like NFS</strong>!</p>
<p>The ultimate state after GPU pooling is that <strong>every application can utilize all GPU resources</strong>. It's why we named our product <strong>Tensor Fusion</strong>.</p>
<p>Here's an interesting analogy - have you wondered <strong>why birds have such small brains yet match mammals in intelligence</strong>?</p>
<p>The human brain structure below has dedicated regions for each sense - like <strong>assigning separate GPUs/vGPUs to each AI model</strong>.</p>
<p><img src="https://filecdn.code2life.top/human-brain.png" alt=""></p>
<p>In contrast, bird brains aren't divided into regions. The cross-section below shows how <strong>each sense can utilize the entire brain for neural computation - this is the Tensor Fusion effect</strong>.</p>
<p><img src="https://filecdn.code2life.top/birds-brain.png" alt=""></p>
<p>While biological evolution can't &quot;undo&quot; mammalian brain structure, we can learn from bird brains in software design. With GPU-CPU separation architecture and fused GPU compute pools, we can <strong>solve all previous issues from a higher dimension</strong>:</p>
<ul>
<li><strong>GPU utilization</strong>: Our GPU-as-service creates a magical control plane that can achieve scheduling, binpacking, and elasticity of both GPU nodes and AI workloads. This <strong>game-changing pool shaping capability</strong> transforms utilization from mediocre to magnificent!</li>
<li><strong>Oversubscription bottlenecks</strong>: GPU-CPU decoupling removes host CPU/Memory constraints on GPU oversubscription. The critical VRAM shortage is easily solved by supplementing with memory/NVMe as &quot;fake VRAM&quot; for low-QoS workloads, swapping to real VRAM in milliseconds when needed, <strong>breaking through the VRAM oversubscription barrier</strong>.</li>
<li><strong>Business-GPU coupling</strong>: With remote GPU pooling, AI infra and AI apps separate concerns. AI apps can run on machines without GPUs and drivers - just auto-injected KB-sized libcuda stub to <strong>run any CUDA program non-intrusively</strong>. The hefty 3-6GB PyTorch/CUDA/CUDA-cudnn images slim down to MB-scale. Decoupling also enables <strong>cross-machine multi-GPU</strong> acceleration to reduce latency and increase throughput.</li>
<li><strong>GPU vendor lock-in</strong>: Tensor Fusion could be used with ZLUDA, which lets you mix different GPU brands in one cluster. All GPUs provide the same CUDA APIs. No need to rewrite code when changing GPU vendors.</li>
<li><strong>Virtualization security</strong>: Remote compute virtualization with local stubs makes it safer. Better fault isolation and memory protection for multi-tenant GPU sharing.</li>
</ul>
<p>While <strong>API remoting compute virtualization and pooling</strong> seems perfect, how feasible is it?</p>
<p>Academia explored this path early on, pioneered by the <a href="https://ieeexplore.ieee.org/abstract/document/5547126/" target="_blank" rel="noreferrer">rCUDA</a> paper with 400+ citations. The basic principle is intercepting CUDA APIs via LD_LIBRARY_PATH or LD_PRELOAD for network forwarding, creating shadow threads/processes on GPU-equipped servers to replay client CUDA calls.</p>
<p><img src="https://filecdn.code2life.top/tensor-fusion-architecuture.png" alt=""></p>
<p><strong>However, destiny's gifts come with hidden costs.</strong></p>
<p>While compute virtualization has an elegant architecture, intercepting and implementing <strong>all compute library APIs</strong> are required, heavily optimizing to avoid network forwarding overhead makes it <strong>far more technically challenging and labor-intensive than the previous three approaches</strong>.</p>
<p>rCUDA stopped updating 4 years ago; GPULess only implemented 60+ CUDA function stubs; commercial product BitFusion was acquired by VMWare and discontinued last year.</p>
<p>Our predecessors proved the feasibility, and now <strong>a new generation of geeks continues exploring this path</strong>. For competitive analysis, I've compiled all similar existing products:</p>
<ul>
<li><a href="https://virtaitech.com/en" target="_blank" rel="noreferrer">VirtAI Technology</a>: Not open source, likely using Remote CUDA API forwarding or a combination of technologies based on product descriptions. VirtAI raised $30M in 2020/2021. They focus on the Chinese market, adapting CUDA for domestic GPU vendors, with no apparent international expansion plans - different from Tensor Fusion's target market.</li>
<li><a href="https://github.com/kevmo314/scuda" target="_blank" rel="noreferrer">Project scuda</a>: A new open source project from 2 months ago. <strong>While its codebase is far less mature than Tensor Fusion, it gained 550+ stars in just 2 months</strong>, showing strong industry demand for a practical rCUDA solution.</li>
<li><a href="https://www.thundercompute.com/" target="_blank" rel="noreferrer">ThunderCompute</a>: Received $500K Pre-Seed funding from YC/AWS/GCP/NVIDIA 5 months ago. They focus on selling compute power, letting clients use remote GPU pools over the internet. We tested internet-based solutions but found latency too high for AI inference. From a business perspective, building GPU pools to sell compute isn't optimal - while they have strong tech, their business strategy needs adjustment.</li>
<li><a href="https://www.juicelabs.co/" target="_blank" rel="noreferrer">JuiceLabs</a>: Raised funding 4 years ago, but no widely-used products or recent funding rounds visible.</li>
</ul>
<p>In summary, only VirtAI and ThunderCompute have achieved commercial impact in CUDA API network forwarding and GPU pool scheduling. <strong>The existing companies prove both the technical feasibility and business value of this approach</strong>.</p>
<p><strong>This is a challenging but correct path that few have taken.</strong></p>
<h2 id="where-are-tensor-fusion-s-opportunities" tabindex="-1">Where are Tensor Fusion's opportunities? <a class="header-anchor" href="#where-are-tensor-fusion-s-opportunities" aria-label="Permalink to &quot;Where are Tensor Fusion's opportunities?&quot;">&ZeroWidthSpace;</a></h2>
<p>With some startups working on similar solutions, <strong>where do we see opportunities for Tensor Fusion</strong>?</p>
<p>Our research shows we don't overlap with other companies using the fourth technical approach. When competing with products using the first three approaches, beyond our <strong>architectural advantages</strong>, we have strong confidence in our <strong>market positioning, product offering, team capabilities, and technical expertise</strong>.</p>
<h3 id="market" tabindex="-1">Market <a class="header-anchor" href="#market" aria-label="Permalink to &quot;Market&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li>The <strong>GPU hardware market</strong> has reached <a href="https://www.fortunebusinessinsights.com/graphic-processing-unit-gpu-market-109831" target="_blank" rel="noreferrer">$61.58 billion in 2024</a>. With a <strong>28.6% CAGR</strong>, it will grow to <strong>$461.02 billion</strong> by 2032. This growth propelled NVIDIA to become the world's most valuable company, holding more than 95% market share in 2024. Assume GPU efficiency management market is mere 1% slice of the GPU hardware market, it represents a $610M opportunity in 2024. Moreover, few AI infrastructure companies compete in this emerging blue ocean market.</li>
<li>For <strong>target market</strong>, Tensor Fusion focuses on serving <strong>global cloud providers and AI SaaS companies with GPU clusters</strong>. Our main competitor is Run.AI, which is using the third type of virtualization and scheduling approach, we're very confident that surpassing Run.AI is just a matter of time.</li>
<li>Our <strong>market strategy</strong> starts with small/medium cloud providers and AI SaaS companies for solution validation, gradually expanding to larger players like HuggingFace and AWS.</li>
</ul>
<h3 id="product" tabindex="-1">Product <a class="header-anchor" href="#product" aria-label="Permalink to &quot;Product&quot;">&ZeroWidthSpace;</a></h3>
<p>In terms of product maturity, Tensor Fusion is currently the <strong>only solution</strong> offering <strong>remote GPU pooling + virtual VRAM expansion + dynamic scheduling</strong> in global market, and has already been <strong>validated in production</strong> by a customer in Asia.</p>
<p>This customer runs an <a href="https://www.tenclass.com/" target="_blank" rel="noreferrer">AI hands-on lab platform</a> where users get access to ComfyUI/SD environments for AI image generation, with customizable workflows and model selection.</p>
<p>After deploying Tensor Fusion, ComfyUI/SD runs on inexpensive CPU-only VMs. GPU resources are only allocated on-demand when users execute image generation tasks. After 10 seconds of inactivity, VRAM is swapped out and the GPU is shared with other users.</p>
<p><strong>The system deployment reduced the costs of the AI lab platform by 90% while solving GPU inventory shortage issues with their cloud provider.</strong></p>
<p>As a product, Tensor Fusion provides an <strong>end-to-end GPU efficiency management solution</strong>, optimizing <strong>scheduling efficiency, observability, and stability</strong> with a focus on serving <strong>cloud providers and AI SaaS companies</strong>.</p>
<p>Our product strategy avoids <strong>competing with customers by building our own compute pools</strong>. Instead, we partner with cloud providers and AI SaaS companies to provide AI infrastructure products and technical consulting, building long-term <strong>product and channel moats</strong>.</p>
<h3 id="team" tabindex="-1">Team <a class="header-anchor" href="#team" aria-label="Permalink to &quot;Team&quot;">&ZeroWidthSpace;</a></h3>
<p>The Tensor Fusion prototype was developed by my friend and former colleague <a href="https://github.com/nooodles2023" target="_blank" rel="noreferrer">Andy</a>, a serial entrepreneur and the creator of a <a href="https://github.com/tenclass/mvisor" target="_blank" rel="noreferrer">mvisor</a>, Andy brings exceptional creativity and low-level programming expertise.</p>
<p>As co-founder, I bring <a href="https://github.com/code2life" target="_blank" rel="noreferrer">technical expertise</a> in IaaS/PaaS and connections with international cloud providers. With experience in product, technology, marketing and management from internal startups, I'm confident in leading business operations and product development.</p>
<p>Post-funding, we plan to formally bring on 2-3 additional talented engineers with strong entrepreneurial drive.</p>
<p>We are actively seeking a <strong>Sales and Operations leader</strong> to join as a potential co-founder, with a focus on <strong>international markets</strong>.</p>
<h3 id="technology" tabindex="-1">Technology <a class="header-anchor" href="#technology" aria-label="Permalink to &quot;Technology&quot;">&ZeroWidthSpace;</a></h3>
<p>Beyond our architectural advantages, we have three key technical differentiators:</p>
<ul>
<li>Proprietary optimizations: Our team's deep CUDA and virtualization expertise has enabled us to develop <strong>patentable innovations in memory-to-VRAM expansion, kernel acceleration, and high-performance protocols</strong>. These create significant technical moats that would take competitors years to replicate, protecting our first-mover advantage.</li>
<li>Advanced scheduling: We're developing <strong>GPU context hot migration</strong> + AI-based dynamic scheduling. This JIT proactive scheduling approach creates a generational advantage over traditional AOT passive allocation via Kubernetes Schedule Plugins.</li>
<li>Seamless integration: Using Kubernetes ecosystem and cross-domain technologies, we've achieved <strong>zero-touch onboarding and zero-config migration, dramatically reducing adoption costs</strong> - a capability unmatched by any existing solution.</li>
</ul>
<h2 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="#conclusion" aria-label="Permalink to &quot;Conclusion&quot;">&ZeroWidthSpace;</a></h2>
<p>This article introduced the background and purpose of GPU virtualization, explaining 4 technical approaches from academia and industry.</p>
<p>Through progressive analysis, we answered <strong>why Tensor Fusion chose API remoting way</strong>, and explored our opportunities in the <strong>AI infrastructure GPU cluster efficiency management</strong> space, along with why we're uniquely positioned to succeed.</p>
<p>My deep belief in Tensor Fusion's value comes from a core truth about cloud computing: <strong>Cloud platforms - whether IaaS, PaaS, or SaaS - create value through efficient resource sharing</strong>.</p>
<p>In IaaS, this mechanism of <strong>abstraction, isolation, and scheduling to enable sharing</strong>, is called <strong>virtualization</strong>.</p>
<p>The essence of virtualization and resource sharing in IaaS lies in transforming physical resources with high marginal costs into logical resources with minimal costs. This transformation happens through robust isolation and precise scheduling, leading to improved efficiency.</p>
<p>Therefore, we believe Tensor Fusion has the opportunity to become a rising star in AI infra, helping the AI wave transform the world.</p>
<p>We are currently seeking <strong>investment partners who understand core technical innovation and have global market resources</strong>. <strong>We welcome inquiries from interested investors</strong>.</p>
<p><img src="https://filecdn.code2life.top/tensor-fusion.png" alt=""></p>
<h2 id="reference" tabindex="-1">Reference <a class="header-anchor" href="#reference" aria-label="Permalink to &quot;Reference&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li>Duato, José, et al. &quot;rCUDA: Reducing the number of GPU-based accelerators in high performance clusters.&quot; 2010 International Conference on High Performance Computing &amp; Simulation. IEEE, 2010.</li>
<li>Reaño, Carlos, and Federico Silla. &quot;Redesigning the rCUDA communication layer for a better adaptation to the underlying hardware.&quot; Concurrency and Computation: Practice and Experience 33.14 (2021): e5481.</li>
<li>Tobler, Lukas. Gpuless–serverless gpu functions. Diss. Master’s thesis. ETH, 2022.</li>
<li>Gu, Jing, et al. &quot;GaiaGPU: Sharing GPUs in container clouds.&quot; 2018 IEEE Intl Conf on Parallel &amp; Distributed Processing with Applications, Ubiquitous Computing &amp; Communications, Big Data &amp; Cloud Computing, Social Computing &amp; Networking, Sustainable Computing &amp; Communications (ISPA/IUCC/BDCloud/SocialCom/SustainCom). IEEE, 2018.</li>
<li>Song, Shengbo, et al. &quot;Gaia scheduler: A kubernetes-based scheduler framework.&quot; 2018 IEEE Intl Conf on Parallel &amp; Distributed Processing with Applications, Ubiquitous Computing &amp; Communications, Big Data &amp; Cloud Computing, Social Computing &amp; Networking, Sustainable Computing &amp; Communications (ISPA/IUCC/BDCloud/SocialCom/SustainCom). IEEE, 2018.</li>
<li>Liu, Zijie, et al. &quot;KubFBS: A fine‐grained and balance‐aware scheduling system for deep learning tasks based on kubernetes.&quot; Concurrency and Computation: Practice and Experience 34.11 (2022): e6836.</li>
<li>Yeh, Ting-An, Hung-Hsin Chen, and Jerry Chou. &quot;KubeShare: A framework to manage GPUs as first-class and shared resources in container cloud.&quot; Proceedings of the 29th international symposium on high-performance parallel and distributed computing. 2020.</li>
<li>Chen, Hung-Hsin, et al. &quot;Gemini: Enabling multi-tenant gpu sharing based on kernel burst estimation.&quot; IEEE Transactions on Cloud Computing 11.1 (2021): 854-867.</li>
<li>Lou, Jie, et al. &quot;ArkGPU: enabling applications’ high-goodput co-location execution on multitasking GPUs.&quot; CCF Transactions on High Performance Computing 5.3 (2023): 304-321.</li>
<li>Hong, Cheol-Ho, Ivor Spence, and Dimitrios S. Nikolopoulos. &quot;GPU virtualization and scheduling methods: A comprehensive survey.&quot; ACM Computing Surveys (CSUR) 50.3 (2017): 1-37.</li>
<li>A Closer Look at VirtIO and GPU Virtualisation | Blog | Linaro. www.linaro.org/blog/a-closer-look-at-virtio-and-gpu-virtualisation</li>
<li>Brief Introduction of GPU Virtualization | Blog | Aliyun. developer.aliyun.com/article/590916</li>
<li>Run TorchServe with Nvidia MPS | Blog | Github. github.com/pytorch/serve/blob/master/docs/nvidia_mps.md</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[为什么Tensor Fusion能够颠覆GPU虚拟化]]></title>
            <link>https://code2life.top/blog/0084-why-tensor-fusion</link>
            <guid>https://code2life.top/blog/0084-why-tensor-fusion</guid>
            <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="为什么tensor-fusion能够颠覆gpu虚拟化" tabindex="-1">为什么Tensor Fusion能够颠覆GPU虚拟化 <a class="header-anchor" href="#为什么tensor-fusion能够颠覆gpu虚拟化" aria-label="Permalink to &quot;为什么Tensor Fusion能够颠覆GPU虚拟化&quot;">&ZeroWidthSpace;</a></h1>
<nav class="table-of-contents"><ul><li><a href="#背景">背景</a></li><li><a href="#为什么需要虚拟化gpu">为什么需要虚拟化GPU</a></li><li><a href="#四种gpu虚拟化技术">四种GPU虚拟化技术</a><ul><li><a href="#硬件和驱动层的共享机制">硬件和驱动层的共享机制</a></li><li><a href="#用虚拟设备实现半虚拟化">用虚拟设备实现半虚拟化</a></li><li><a href="#共置任务调度的仿虚拟化">共置任务调度的仿虚拟化</a></li><li><a href="#基于计算库api远程转发的算力虚拟化">基于计算库API远程转发的算力虚拟化</a></li></ul></li><li><a href="#tensor-fusion的机会在哪里">Tensor Fusion的机会在哪里？</a><ul><li><a href="#市场">市场</a></li><li><a href="#产品">产品</a></li><li><a href="#团队">团队</a></li><li><a href="#技术">技术</a></li></ul></li><li><a href="#总结">总结</a></li><li><a href="#reference">Reference</a></li></ul></nav>
<h2 id="背景" tabindex="-1">背景 <a class="header-anchor" href="#背景" aria-label="Permalink to &quot;背景&quot;">&ZeroWidthSpace;</a></h2>
<p>最近在搞公司的云成本治理，大多数云资源成本控制思路很明确，但贵到离谱的GPU费用，一直是头疼的问题。</p>
<p>十月跟一位好友叙旧，碰巧他正在研究GPU虚拟化，看完他的原型演示，直觉告诉我这是个<strong>颠覆性技术</strong>，甚至有机会创造出<strong>独角兽级别的企业</strong>。</p>
<p>我们一拍即合，业余时间支棱起来，项目名为<a href="https://tensor-fusion.ai/" target="_blank" rel="noreferrer">Tensor Fusion</a>。</p>
<p>随着对GPU虚拟化的研究深入，我整理了一些干货，既回答我们自己<strong>为什么要投身这件事</strong>，也回答用户和投资者<strong>我们的产品价值在哪</strong>。</p>
<p>在展开技术讨论之前，可以先看下Demo，了解Tensor Fusion是什么，使用文档在这里：<a href="https://tensor-fusion.ai/guide/get-started" target="_blank" rel="noreferrer">Get Started</a>，欢迎试用、反馈。</p>
<video-player src="https://filecdn.code2life.top/TensorFusion-demo.mp4" poster="https://filecdn.code2life.top/tfs-no-play-poster.png" /><p><strong>读完这篇文章，下面几个问题你也会有答案。</strong></p>
<ul>
<li>为什么需要GPU虚拟化？</li>
<li>实现GPU虚拟化有哪些技术，核心原理分别是什么？</li>
<li>这些GPU虚拟化技术的优缺点是什么？</li>
<li>为什么业界至今没有出现一个完美的GPU虚拟化和池化方案？</li>
<li>凭什么说我们有机会颠覆这个领域，哪来的勇气和自信？</li>
</ul>
<h2 id="为什么需要虚拟化gpu" tabindex="-1">为什么需要虚拟化GPU <a class="header-anchor" href="#为什么需要虚拟化gpu" aria-label="Permalink to &quot;为什么需要虚拟化GPU&quot;">&ZeroWidthSpace;</a></h2>
<p>在调查公司的GPU成本问题时，我看到每个服务实例独占GPU，虽然每个GPU在业务高峰期使用率能达到70%，但<strong>整个GPU集群的综合使用率却从来没有超过20%</strong>。</p>
<p>这个例子充分说明了为什么需要GPU虚拟化。如果不做虚拟化，就没法<strong>安全地共享GPU</strong>，也就是浪费了<strong>80%的资源，给云厂商多付了400%的钱</strong>！</p>
<p><strong>用专业语言说，GPU虚拟化作用在于：</strong></p>
<ol>
<li>虚拟化是<strong>共享</strong>底层物理资源的手段，可以避免浪费昂贵的GPU资源</li>
<li>虚拟化能够隔离故障、内存地址、控制配额，这是<strong>安全实现多租户的前提</strong></li>
<li>虚拟化是<strong>弹性扩缩容的基础</strong>，能够在业务高并发时，<strong>减小尾部延迟，提升吞吐量</strong></li>
</ol>
<p>IaaS发展这么多年，CPU虚拟化已经近乎完美了，但GPU竟然还在挂载调用物理设备，明显不合常理。</p>
<p>那GPU能虚拟化么？可以，业界有哪些方案，分别存在什么问题呢？</p>
<h2 id="四种gpu虚拟化技术" tabindex="-1">四种GPU虚拟化技术 <a class="header-anchor" href="#四种gpu虚拟化技术" aria-label="Permalink to &quot;四种GPU虚拟化技术&quot;">&ZeroWidthSpace;</a></h2>
<p>目前业界有四类解决方案，我们按照抽象层次<strong>从低到高</strong>逐个展开：</p>
<ol>
<li>硬件和驱动层自带的共享机制，<strong>严格意义上不属于虚拟化</strong></li>
<li>分出虚拟设备实现<strong>半虚拟化</strong></li>
<li>共置任务调度的<strong>仿虚拟化</strong></li>
<li>基于计算库API转发的<strong>算力虚拟化</strong></li>
</ol>
<h3 id="硬件和驱动层的共享机制" tabindex="-1">硬件和驱动层的共享机制 <a class="header-anchor" href="#硬件和驱动层的共享机制" aria-label="Permalink to &quot;硬件和驱动层的共享机制&quot;">&ZeroWidthSpace;</a></h3>
<p>GPU硬件和驱动层，一般会自带多用户的隔离和共享机制，每个GPU厂商的做法都不一样。</p>
<p>以NVIDIA为例，自带的共享机制主要有3类：</p>
<ol>
<li><a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/" target="_blank" rel="noreferrer">Multi-instance GPU</a> (MIG) ，<strong>多实例GPU</strong>。类似切蛋糕，把显卡切成完全隔离的几份。但跟葫芦娃一样，最多只能变出来7个，显存隔离是GB级起步，只有2020年的<strong>Ampere架构</strong>之后才支持MIG。</li>
<li><a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html#comparison-time-slicing-and-multi-instance-gpu" target="_blank" rel="noreferrer">Time-slicing</a>，时间片轮转。本质上是显卡默认的多进程共享模式，没有内存隔离、故障隔离。NVIDIA在Kubernetes中的实现也很简单粗暴，<strong>假装有多个GPU，背后就是同一个设备</strong>，塞给多个Pod用而已，其实没法限制每个Pod内再启动N个进程抢更多的时间片。Time-slicing就好比<strong>聚餐时轮流捞火锅</strong>，谁真要一勺子捞走一锅肉也没办法。</li>
<li><a href="https://docs.nvidia.com/deploy/mps/index.html" target="_blank" rel="noreferrer">Multi-process Service</a> (MPS), 多进程GPU服务。MPS是Time-slicing的变体，大致原理是让多进程共享CUDA Context，MPS调度器就能见缝插针，让多个任务在有闲置资源时<strong>并行</strong>，而不是Time-slicing的<strong>并发+CUDA上下文切换</strong>。在2017年之前，NVIDIA提供了mps-server在软件层调度，2017年之后的Volta架构GPU，引入了Hyper-Q硬件调度，MPS模式同时使用GPU的进程数数也扩大到了48个，显存地址空间隔离也做了，但没有内存OOM保护和故障隔离，大多数情况<a href="https://github.com/pytorch/serve/blob/master/docs/nvidia_mps.md" target="_blank" rel="noreferrer">效率高于Time-slicing</a>。MPS可以通俗理解成<strong>餐厅叫号</strong>，只要桌子空出来就能进去吃了。</li>
</ol>
<p>总结一下，<strong>MIG是空分复用（Space division multiplexing），Time-slicing和MPS是时分复用（Time division multiplexing）</strong>，详细的对比可以参考<a href="https://github.com/rh-aiservices-bu/gpu-partitioning-guide" target="_blank" rel="noreferrer">这篇文档</a>。</p>
<p><img src="https://filecdn.code2life.top/nvidia-mig.png" alt=""></p>
<p><strong>MIG和Time-slicing/MPS结合，可以实现类似GPU虚拟化的效果，但粒度太粗，无法从根本上提高GPU使用率</strong>，也都不可能做到<strong>显存超卖</strong>，如果用了Time-slicing，还会带来<strong>降低可用性、增加延迟</strong>的风险。</p>
<p>在实际应用中，虽然这种方案做不到对GPU的<strong>细粒度资源控制</strong>，但好在简单易行，搭配上Kubernetes集群自带的池化调度能力，<strong>能满足最基本的业务需求</strong>。</p>
<p>社区有个开源项目<a href="https://nebuly-ai.github.io/nos/dynamic-gpu-partitioning/partitioning-modes-comparison" target="_blank" rel="noreferrer">Nebuly NOS</a>，基于NVIDIA的MIG+MPS在Kubernetes中<strong>动态切分GPU设备</strong>，比NVIDIA提供的原生Kubernetes方案更自动化、调度效果更好。</p>
<h3 id="用虚拟设备实现半虚拟化" tabindex="-1">用虚拟设备实现半虚拟化 <a class="header-anchor" href="#用虚拟设备实现半虚拟化" aria-label="Permalink to &quot;用虚拟设备实现半虚拟化&quot;">&ZeroWidthSpace;</a></h3>
<h4 id="_1-虚拟设备是什么" tabindex="-1">1. 虚拟设备是什么？ <a class="header-anchor" href="#_1-虚拟设备是什么" aria-label="Permalink to &quot;1. 虚拟设备是什么？&quot;">&ZeroWidthSpace;</a></h4>
<p>虚拟I/O设备在IaaS领域发展多年了，这条路算是”原教旨主义GPU虚拟化”。</p>
<p>实现时虚拟设备，为了尽可能避免性能损失，一般不会用纯软件模拟设备做<strong>全虚拟化</strong>，而是采用兼顾性能和安全的<strong>半虚拟化</strong>。</p>
<p>大体思路是：把危险的<strong>设备控制层</strong>隔离在Hypervisor、把<strong>设备的数据和功能层</strong>直通(Passthrough)到虚拟机中。</p>
<p>虚拟设备在技术层面比较复杂，比如用IOMMU隔离内存页表，隔离驱动函数指针等等，这里不再展开。</p>
<p><img src="https:///filecdn.code2life.top/virtual-gpu-technology-vgpu-11-software-stack.jpg" alt=""></p>
<h4 id="_2-实现虚拟设备的3种变体" tabindex="-1">2. 实现虚拟设备的3种变体 <a class="header-anchor" href="#_2-实现虚拟设备的3种变体" aria-label="Permalink to &quot;2. 实现虚拟设备的3种变体&quot;">&ZeroWidthSpace;</a></h4>
<p>实现GPU虚拟设备的技术有3种变体，VFIO + SR-IOV, GRID vGPU, VirtIO。</p>
<ol>
<li><strong>VFIO + SR-IOV</strong>。VFIO怎么理解呢？我们把VFIO拆成“VF”和“IO”，VF就是虚拟函数(Virtual Function)，虚机调用VF，设备再映射成PF(Physical Function)。那VF是怎么实现呢，答案就是后面的<strong>SR-IOV</strong>。SR-IOV这是一种PCIe设备虚拟化标准，硬件厂商支持了这种标准，Hypervisor就能按这个标准来管理“设备分身”了，GPU虚拟化用VFIO+SR-IOV的主要是AMD，性能损失在4%以下。</li>
<li><strong>GRID vGPU</strong>。GRID vGPU是NVIDIA独有的商业化虚拟设备方案。NVIDIA很久之前就搞出了GRID vGPU，不仅不开源，还单独收费，License挺贵的，逼的云厂商也得想办法自研NVIDIA显卡的虚拟化。技术细节不展开，大体上是用Mediated Device (mdev)和修改设备驱动，实现了类似VFIO + SR-IOV的效果。毕竟NVIDIA已经世界第一市值了，还遵循什么标准，他自己就是标准。除了NVIDIA, Intel显卡虚拟化技术Intel GVT实现思路类似。</li>
<li><strong>VirtIO</strong>。在SRV标准推广之前，VirtIO技术就存在了。实现思路很简单，是给虚拟机注入一个“假的驱动”，Hypervisor再从Host-Guest共享内存中读取I/O请求，转发到宿主上“真的驱动”，最后通过共享内存返回给Guest VM，完成一次设备I/O。VirtIO引入了“驱动前端”和“驱动后端”两个概念，在VM中看到的只是一个可以灵活修改的驱动前端，性能损失比前两者稍高一些，但灵活性强。在GPU虚拟化方面，社区有一个<a href="https://github.com/coldfunction/qCUDA" target="_blank" rel="noreferrer">qCUDA</a> 玩具级项目用的是VirtIO。</li>
</ol>
<p>在应用方面，卖GPU虚机的云厂商最需要传统的虚拟设备技术方案，因此各大云厂也自研出各种类似的方案，比如 <a href="https://www.alibabacloud.com/help/en/egs/what-is-cgpu" target="_blank" rel="noreferrer">阿里云cGPU</a> 、<a href="https://support.huaweicloud.com/intl/en-us/usermanual-hce/hce_xgpu_0002.html" target="_blank" rel="noreferrer">华为云xGPU</a>。</p>
<h4 id="_3-为什么虚拟设备不是终解" tabindex="-1">3. 为什么虚拟设备不是终解 <a class="header-anchor" href="#_3-为什么虚拟设备不是终解" aria-label="Permalink to &quot;3. 为什么虚拟设备不是终解&quot;">&ZeroWidthSpace;</a></h4>
<p>虚拟设备看起来已经不错了，对<strong>单个GPU设备</strong>的显存控制能到MB级别，算力能控制到1%/10%级别，在OS内核态运行，安全性也有成熟技术的保障。</p>
<p>GPU虚拟化到此为止了吗？</p>
<p>当然没有。</p>
<p>我们只要问一个问题：<strong>用户需要什么？</strong></p>
<p>用户是要一台带GPU的虚机吗？是GPU上的6912个CUDA core，4200MB VRAM吗？</p>
<p>都不是，我们从<strong>第一性原理</strong>思考。</p>
<p>用户要的是：<strong>能够进行各类神经网络模型的训练/推理，完成业务目标</strong>。所以，要有一种“东西”，帮Ta完成<strong>以每秒万亿浮点数计的张量计算</strong>。</p>
<p>那有没有办法，在有限的GPU资源池中，尽可能<strong>又快、又多、又安全</strong>的完成多个租户提交的计算任务呢？</p>
<p>思考到这个维度，GPU设备本身的虚拟化就不再重要了，<strong>设备提供算力，算力的隔离和共享 -- 算力的虚拟化</strong>，才能从根本上满足用户需求。</p>
<p>再从<strong>业务</strong>角度看，虚拟设备这条路有什么局限性呢？</p>
<ol>
<li>虚拟设备的<strong>资源配额是固定</strong>的，不能跟随<strong>业务</strong>峰谷自动调整，导致动态调度空间不大，<strong>整体资源利用率还是上不去</strong></li>
<li>能超卖CUDA Core，但一般没法超卖显存(VRAM)，二者比例失衡，VRAM极有可能成为超卖瓶颈，<strong>业务</strong>调度不上去</li>
<li>物理GPU设备必须挂在宿主机上，还必须要在<strong>业务</strong>运行环境设备挂载、安全驱动，影响弹性、增加管理复杂度</li>
<li>不可能做到跨机多卡共同加速一组计算任务，没办法降低<strong>业务延迟</strong></li>
<li>过于依赖硬件厂商，没办法构建多个厂商GPU设备混一起的异构集群，<strong>业务</strong>还是很容易被GPU厂商锁定</li>
</ol>
<h3 id="共置任务调度的仿虚拟化" tabindex="-1">共置任务调度的仿虚拟化 <a class="header-anchor" href="#共置任务调度的仿虚拟化" aria-label="Permalink to &quot;共置任务调度的仿虚拟化&quot;">&ZeroWidthSpace;</a></h3>
<p>在虚拟设备的局限性中，第一条最严重：<strong>当业务有明显峰谷时，GPU利用率还是上不去</strong>。</p>
<p>因此，市面上还有第三类方案，专注于解决GPU利用率问题，这类方案的关键创新在于：<strong>把调度粒度从粗的设备级别，下钻到了细的计算任务级别。</strong></p>
<p>相当于在设备之上，抽象出了一个“算力中介”，用户只要告诉这个中介自己要算什么，至于用哪些GPU上的哪些StreamMultiprocessor去算，由中介统一调度。也就是说，虚拟化的对象，<strong>从GPU设备，升维成算力</strong>。</p>
<p>就像盖房子，一般会去找包工头，包工头可以同时接多个项目，按事情的最优顺序一件一件分给施工队，对于施工队来说，抽象出“包工头”后，整体效率远高于让客户直接雇佣工人。因为，<strong>调度粒度从”工人“变成了“任务”</strong>。</p>
<p>具体怎么实现呢？首先系统允许多个应用并发使用GPU执行计算任务，这些任务叫<strong>共置任务</strong>（Co-location tasks），应用层会调用用户态计算库，比如NVIDIA libcuda和AMD HIP SDK，那么，在这里<strong>横切一刀，截面上加一个限流器</strong>，控制这些共置任务<strong>如何流进物理设备，就能实现精准的资源配额</strong>。</p>
<p>这个截面，通常是用LD_LIBRARY_PATH / LD_PRELOAD两把刀切开的，完全在用户态处理，性能损失极低。
限流器一般用令牌桶算法，桶里剩多少，是异步线程调用nvml库获取的GPU实时监控数据判断的。</p>
<p>调度器上层，再通过Kubernetes Device Plugin暴露算力配额接口给用户，让用户在resources中写上类似&quot;<strong>nvidia.com/vgpu: 1%</strong>&quot; 的requests/limits，搭配原生的或定制的Kubernetes调度器，实现集群级别的池化算力分配。</p>
<p>这样实现效果很像虚拟设备，但并没有做内存地址隔离和故障隔离，不能算严格意义的虚拟化，我们姑且叫<strong>共置任务调度的仿虚拟化</strong>。</p>
<p>下面是学界和业界比较典型的几个实现：</p>
<ul>
<li><strong><a href="https://github.com/tkestack/vcuda-controller" target="_blank" rel="noreferrer">Gaia GPU</a></strong>：这篇2018年<a href="https://ieeexplore.ieee.org/document/8672318" target="_blank" rel="noreferrer">腾讯和北大发的论文</a>的实现方案，论文有43次引用，其他近几年类似研究大多是GaiaGPU的后续优化</li>
<li><strong><a href="https://github.com/NTHU-LSALAB/KubeShare" target="_blank" rel="noreferrer">KubeShare</a> &amp; <a href="https://github.com/NTHU-LSALAB/Gemini" target="_blank" rel="noreferrer">Kernel Burst</a></strong>：引入Kernel burst概念和预测任务执行机制，进一步提升了调度效率，实现了单GPU卡上的Auto Scale</li>
<li><strong><a href="https://link.springer.com/article/10.1007/s42514-023-00154-y" target="_blank" rel="noreferrer">Ark GPU</a></strong>：引入了负载预测模型提升任务调度效率，并且区分LC(Latency-Critical)和BE(Best-Effort)两种不同的QoS，控制调度优先级</li>
<li><strong><a href="https://github.com/Project-HAMi/HAMi" target="_blank" rel="noreferrer">Project HAMI</a></strong>：多个云厂商共建的CNCF Sandbox项目，以前叫<a href="https://github.com/4paradigm/k8s-vgpu-scheduler" target="_blank" rel="noreferrer">k8s-vGPU-scheduler</a>，侧重于在业界落地，支持了更多GPU设备厂商，关键的拦截和调度控制代码在<a href="https://github.com/Project-HAMi/HAMi-core/blob/main/src/cuda/hook.c" target="_blank" rel="noreferrer">HAMi-core</a>，和<strong>GaiaGPU底层代码几乎一模一样</strong></li>
<li><a href="https://run.ai" target="_blank" rel="noreferrer"><strong>RUN AI</strong></a>：一家已经融资了$1.18亿的以色列创业公司产品，从Demo看，很有可能也是借鉴了GaiaGPU，但做了更多的企业级功能，比如动态调度、GPU集群控制台。</li>
</ul>
<p><img src="https://filecdn.code2life.top/gaia-gpu.png" alt=""></p>
<p>还有一个有意思的项目是<strong>HuggingFace <a href="https://huggingface.co/docs/hub/spaces-zerogpu" target="_blank" rel="noreferrer">ZeroGPU</a></strong>，是HuggingFace CEO Clem Delangue在今年刚投了一千万刀，建设了免费使用的A100推理集群，降低AI开发者门槛，半公益半商业化性质。</p>
<p>HuggingFace ZeroGPU关键代码在Gradio SDK，从源码看，这个项目Hook的是更高层的Pytorch API，而不是底层的NVIDIA Driver API，搭配Gradio SDK中实现的@<strong>space.GPU装饰器</strong>，拦截Python推理函数，让调度器对GPU资源进行配额判断，配额不足就让任务等待，足够就本地8xA100选择GPU执行推理，当函数冷却后，把显存从GPU中置换出来，放入NVMe盘。</p>
<p><img src="https://filecdn.code2life.top/zero-gpu.png" alt=""></p>
<p>ZeroGPU这种Hook高层API也能做到GPU分时复用和QoS，但没法细粒度控制每个应用能使用多少VRAM，比如ZeroGPU就允许每个应用最多占据一个完整的A100-40G VRAM。</p>
<p>总结一下，第三类共置任务调度的算力虚拟化方案，已经把<strong>虚拟化的对象做到了算力这一层</strong>，对AI底层计算库加装限流器，再配合Kubernetes自带的池化静态调度，实现了相对灵活的资源控制和多租户共享。</p>
<p><strong>但这还不够，为什么呢？</strong></p>
<p>除了上述虚拟设备路线的<strong>问题2/3/4/5没有解决</strong>，从整个GPU池的角度看，还有这几个新问题没有解决：</p>
<ul>
<li>仍然没有脱离GPU设备的桎梏，CPU部分和GPU部分的调度耦合在一起，无法独立扩缩容，做不到GPU部分的Scale to Zero再亚秒级Warm-up。</li>
<li>拉远视角看整个GPU集群，虽然借助Kubernetes的分布式调度器部分实现了池化，但没有做主动碎片整理、GPU池本身的扩缩容，调度效率提升不到下一个Level，运维成本仍然很高（上面提到的方案只有 run.ai 做了主动调度和碎片整理）</li>
<li>都没有做故障隔离、内存地址隔离，在多个不可信租户共享的场景下不够安全。</li>
</ul>
<p>那是否可以沿着这条路<strong>再往后想一层</strong>，彻底解决这些问题呢？</p>
<h3 id="基于计算库api远程转发的算力虚拟化" tabindex="-1">基于计算库API远程转发的算力虚拟化 <a class="header-anchor" href="#基于计算库api远程转发的算力虚拟化" aria-label="Permalink to &quot;基于计算库API远程转发的算力虚拟化&quot;">&ZeroWidthSpace;</a></h3>
<p>我们回到第一性原理继续思考下去，找根本解。</p>
<p>既然要<strong>极致的共享GPU算力</strong>，就得想办法把<strong>所有算力集中到一个大池</strong>，<strong>对整池进行细粒度调度</strong>。</p>
<p>就像解决IaaS层的存储效率问题，分布式的NFS/ObjectStorage已经成了事实标准。NFS把硬盘变成远端存储服务，实现了存算分离架构。</p>
<p>以此类推，如果把<strong>GPU变成远端算力服务</strong>，实现<strong>GPU-CPU分离</strong>，就能把整个GPU集群实现<strong>算力融合+细粒度调度控制的</strong>架构，<strong>就像把GPU当成NFS用</strong>。</p>
<p>GPU独立池化后的极致状态是，<strong>每个应用都可以用到所有的GPU资源</strong>。这种AI算力融合的架构，也是我们把产品名定为<strong>Tensor Fusion</strong>的原因。</p>
<p>举个通俗的例子，大家是否想过，<strong>为什么鸟类脑子那么小，却跟哺乳动物有同级别的智能</strong>？</p>
<p>下图是人类大脑结构，处理每种感觉都有独立的脑区，相当于<strong>为每个AI模型分配了独立的GPU/vGPU</strong>。</p>
<p><img src="https://filecdn.code2life.top/human-brain.png" alt=""></p>
<p>而鸟类的大脑结构是不分脑区的，下图是鸟类大脑的截面图，<strong>每种感觉都可以利用全脑做神经计算，这就是Tensor Fusion的效果</strong>。</p>
<p><img src="https://filecdn.code2life.top/birds-brain.png" alt=""></p>
<p>生物演化很难“撤回”，哺乳动物的大脑只能继续“大力砖飞”了，而人为设计的软件，不妨借鉴下鸟类的大脑。实现GPU-CPU分离架构后，融合了GPU计算池，上面提到的所有问题都能<strong>从更高维度彻底解决</strong>：</p>
<ul>
<li><strong>GPU利用率问题</strong>：GPU as service后，造出了一个跟业务解耦的控制面，能够实现复杂的调度策略、资源碎片整理、甚至是业务无感的Scale to Zero、GPU物理池的自动扩缩容。这种对<strong>GPU池使用率极致的整形能力</strong>，产生了GPU利用率的质变。</li>
<li><strong>超卖资源短板问题</strong>：GPU-GPU解耦，宿主上的CPU/Memory不再成为GPU超卖瓶颈。最关键的显存不够问题也能轻松解决，很容易实现内存/NVMe找补显存，这些慢一点的“假显存”分配给低QoS的业务使用，收到推理请求时，在毫秒/亚秒级置换到真显存中，<strong>打破显存超卖瓶颈</strong>。</li>
<li><strong>业务和GPU设备耦合问题</strong>：GPU远程池化后，AI Infra和AI App的关注点分离，允许AI业务跑在没有驱动、没有GPU的CPU机器上，系统只需要自动注入一个KB级别的libcuda stub，就能在<strong>不侵入业务的情况下让任意CUDA程序跑起来</strong>。而动辄3-6GB的Pytorch/CUDA/CUDA-cudnn镜像，也能瘦身到MB级别。解耦后对业务还有一个惊喜，远程GPU池化架构很容易做到<strong>跨机多卡</strong>的计算加速，降低业务延迟，增加吞吐量。</li>
<li><strong>GPU厂商锁定问题</strong>：当业务不再依赖底层驱动库和CUDA Runtime，就有办法借助类似ZLUDA的技术，构建多个厂商GPU设备混在一起的异构集群，对业务层提供一致的CUDA API。而不用像现在一样，换一种GPU要把整个业务和Pytorch改一遍。</li>
<li><strong>虚拟化的安全问题</strong>：算力虚拟化的实现在远端，业务侧只有一个Stub，那么故障隔离、内存地址隔离都更容易实现，让多个不互信租户共享GPU池。</li>
</ul>
<p>既然<strong>基于计算库API远程转发做算力虚拟化和池化</strong>看上去很完美，可行性如何呢？</p>
<p>其实学术界在很早之前就探索过这条路，先驱是2010年发表的、已被引用400多次的<a href="https://ieeexplore.ieee.org/abstract/document/5547126/" target="_blank" rel="noreferrer">rCUDA</a>论文，大致原理是，通过LD_LIBRARY_PATH或LD_PRELOAD拦截CUDA API，进行网络转发，到有实际GPU设备的服务端创建一个影子线程或进程，重放客户端的CUDA调用。</p>
<p><img src="https://filecdn.code2life.top/gpuless-arch.png" alt=""></p>
<p><strong>然而，命运的馈赠早已暗中标好了价格</strong>。</p>
<p>算力虚拟化看上去架构很完美，但要拦截和实现<strong>所有的计算库API</strong>，还要做大量的<strong>底层优化</strong>来避免网络转发带来的性能影响，<strong>技术难度、工作量都远高于前三种</strong>。</p>
<p>rCUDA在4年前停止更新了；GPULess只实现了60多个CUDA函数的Stub；商业产品BitFusion被VMWare收购后去年也停止维护了。</p>
<p>前辈们验证了可行性，后浪推前浪，现在<strong>仍然有一群极客在这条路上继续探索。为了做竞品分析，我整理了所有类似的现存产品</strong>：</p>
<ul>
<li><a href="https://virtaitech.com/en" target="_blank" rel="noreferrer">趋动科技Virt AI</a>：没开源，从产品介绍中推测可能用的是Remote CUDA API转发方案，或是组合了几类技术。Virt AI在2020/2021年拿到了拿到了$30M融资。市场主要在国内，做多个国产GPU厂商的CUDA适配，没有看到出海的意愿，因此和我们Tensor Fusion的目标市场不一样</li>
<li><a href="https://github.com/kevmo314/scuda" target="_blank" rel="noreferrer">Project scuda</a>：两个月前刚出现的开源项目，<strong>看源码离我们Tensor Fusion的成熟度还差的非常多，但仅2个月就已经550多Star了</strong>，可见业界有很多人在等一个真正能用的rCUDA方案。</li>
<li><a href="https://www.thundercompute.com/" target="_blank" rel="noreferrer">ThunderCompute</a>：5个月之前刚拿到YC/AWS/GCP/NVIDIA的Pre Seed轮$500K的融资，目前方向是卖算力，允许客户端通过本地机器走互联网用远程GPU池。其实我们也尝试过走互联网，测试发现对AI推理业务延迟影响很大；而且从商业视角看，自建GPU池卖算力我认为不是最优商业模式，这家初创公司的技术应该也很强，但商业战略错了，迟早会撞南墙的。</li>
<li><a href="https://www.juicelabs.co/" target="_blank" rel="noreferrer">JuiceLabs</a>: 4年前融资的，目前没看到广泛使用的产品，也没看到近年的融资记录。</li>
</ul>
<p>总结一下，目前在CUDA API网络转发做GPU大池调度路线上，能产生商业影响力的，只有VirtAI和ThunderCompute两家公司，<strong>现存的几家公司，恰恰证明了这条路的可行性和商业价值</strong>。</p>
<p><strong>这是一条少有人走的路，难，且正确</strong>。</p>
<h2 id="tensor-fusion的机会在哪里" tabindex="-1">Tensor Fusion的机会在哪里？ <a class="header-anchor" href="#tensor-fusion的机会在哪里" aria-label="Permalink to &quot;Tensor Fusion的机会在哪里？&quot;">&ZeroWidthSpace;</a></h2>
<p>既然有个别创业公司在做类似的事情，<strong>我们做Tensor Fusion的机会在哪呢</strong>？</p>
<p>从调研结果看，我们和这第四类技术路线的几家公司<strong>目标市场不重合</strong>；而跟前三种技术路线产品的正面交锋，除了上面分析的<strong>架构优势</strong>，我们从<strong>市场、产品、团队、技术方面看，都有足够的底气</strong>。</p>
<h3 id="市场" tabindex="-1">市场 <a class="header-anchor" href="#市场" aria-label="Permalink to &quot;市场&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li>从<strong>市场规模</strong>看，<strong>GPU硬件市场</strong>在2024年已经达到了<a href="https://www.fortunebusinessinsights.com/graphic-processing-unit-gpu-market-109831" target="_blank" rel="noreferrer">615.8亿美元</a>，<strong>年复合增长率28.6%</strong>，在2032年预计年营收达到4610.2亿美元，这个市场前景让95%市场份额的NVIDIA成为了世界第一市值公司。那么，<strong>GPU管理和优化的SaaS，哪怕只占硬件市场规模的1%，仅看2024年，也至少有6.1亿美元的潜在市场规模</strong>，而目前只有<strong>极个别AI Infra公司在做这个细分领域，市场一片蓝海</strong>。</li>
<li>从<strong>目标市场</strong>看，Tensor Fusion面向<strong>海外云厂商、拥有GPU集群的AI SaaS</strong>，和国内相对较为成熟的趋动科技(Virt AI)错开生态位。因此，目标市场有重叠的，主要是选择第三种技术路线的Run.AI，超越这家公司只是时间问题。</li>
<li>从<strong>市场细分</strong>看，我们会先从中小型云厂商、AI SaaS开始，做方案验证，等落地成熟后，逐步向HuggingFace、AWS这些中大型云厂商/AI SaaS推广方案。</li>
</ul>
<h3 id="产品" tabindex="-1">产品 <a class="header-anchor" href="#产品" aria-label="Permalink to &quot;产品&quot;">&ZeroWidthSpace;</a></h3>
<p>产品成熟度上，目前国际市场上<strong>没有任何产品</strong>能做到<strong>远程GPU池 + 虚拟显存扩充 + 动态调度</strong>，Tensor Fusion不仅是<strong>独一份</strong>，而且已经在一家公司<strong>落地验证</strong>了。</p>
<p>这家公司有个<a href="https://www.tenclass.com/" target="_blank" rel="noreferrer">AI动手实验室产品</a>，用户购买后能得到一个ComfyUI/SD环境学习AI绘图，用户可以自定义绘图流，选择不同的AI模型。</p>
<p>用了Tensor Fusion之后，ComfyUI/SD部署在廉价的纯CPU-based VM上，仅当用户执行绘图动作时，<strong>按需调度远程GPU池进行模型推理，绘图完成后10秒不活跃后将显存置换出去</strong>，GPU再共享给其他用户用。</p>
<p><strong>系统上线后，为这家公司至少降低了AI Hands-on Lab产品90%的成本，顺便解决了云厂商GPU库存不足购买失败问题</strong>。</p>
<p>产品形态上，Tensor Fusion提供<strong>端到端的GPU效率管理方案</strong>，把<strong>调度效率、可见性、稳定性</strong>做到极致，专注于<strong>服务云厂商、AI SaaS</strong>。</p>
<p>产品战略上，我们<strong>不会去自建算力池跟客户抢蛋糕</strong>，而是与云厂商/AI SaaS合作双赢，为他们提供更多的AI Infra产品、技术咨询服务，在长期形成<strong>产品壁垒和渠道壁垒</strong>。</p>
<h3 id="团队" tabindex="-1">团队 <a class="header-anchor" href="#团队" aria-label="Permalink to &quot;团队&quot;">&ZeroWidthSpace;</a></h3>
<p>Tensor Fusion原型产品，是我前同事和老朋友<a href="https://github.com/nooodles2023" target="_blank" rel="noreferrer">Andy</a>开发的，Andy是连续创业者，Tensor Fusion的创始人，也是<a href="https://github.com/tenclass/mvisor" target="_blank" rel="noreferrer">手搓虚拟机</a>的极客，创造力和底层编码能力非常强。</p>
<p>作为创始人之一，我在IaaS/PaaS领域有一些<a href="https://github.com/code2life" target="_blank" rel="noreferrer">技术见解</a>，工作过程也积累了不少海外云厂商资源。这些年在公司内算是连续创业，产品、技术、营销、管理都有涉及，有信心做好公司运营、团队管理。</p>
<p>我们计划融资后，再正式邀请2-3位有强烈创业意向的优秀研发。</p>
<p>目前创始团队的<strong>销售和运营负责人虚位以待</strong>，我正在想办法在<strong>海外</strong>寻找这位潜在的Co-founder。</p>
<h3 id="技术" tabindex="-1">技术 <a class="header-anchor" href="#技术" aria-label="Permalink to &quot;技术&quot;">&ZeroWidthSpace;</a></h3>
<p>除了上面分析的架构优势，具体到技术细节，我们有三个关键优势。</p>
<ul>
<li>首先是底层优化，凭借团队对CUDA的深入理解和算力虚拟化落地经验，<strong>Tensor Fusion实现了内存补显存、launchKernel函数的底层优化、高性能通信协议</strong>等等，这些技术壁垒短期不太可能被超越。</li>
<li>其次是调度器，我们正在开发GPU上下文热迁移 + 基于AI预测的动态算力调度器，<strong>让每个推理业务能够跨机利用大池中的GPU资源</strong>。JIT主动调度，相比于传统的Kubernetes Scheduler Plugin的AOT被动分配，会跟业界现有方案形成代差。</li>
<li>最后是无缝接入能力，基于Kubernetes生态和一些“跨界技术”，实现了<strong>业务0侵入接入、0配置迁移，极大降低了用户的迁移和采纳成本</strong>，这个技术是任何现有方案找不到的。</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文介绍了GPU虚拟化的背景和目的，展开讲解了学界和业界四种GPU虚拟化技术路线。</p>
<p>通过层层递进的分析，回答了<strong>为什么Tensor Fusion选择了计算库API远程转发的算力虚拟化</strong>这条路，以及在<strong>AI Infra的GPU集群效率管理</strong>这个赛道上，Tensor Fusion的机会在哪，为什么是我们能做成这件事。</p>
<p>我自己坚信Tensor Fusion的价值，源自对云计算的理解：<strong>不管是IaaS/PaaS/SaaS，云的根本价值是共享带来的效能质变</strong>。</p>
<p>在IaaS领域，这种<strong>抽象、隔离、调度实现共享的机制</strong>，就叫<strong>虚拟化</strong>。</p>
<p>而虚拟化的本质，或者说IaaS中共享的本质，是把<strong>高边际成本的物理资源，抽象、封装成极低边际成本的逻辑资源，通过对逻辑资源的隔离和调度，共享了物理资源，提升了效能</strong>。</p>
<p>逻辑资源的抽象越接近用户需求，物理资源的调度越细致，云的<strong>能效比</strong>就越高。</p>
<p>因此，我们相信Tensor Fusion有机会成为AI Infra领域新星，助力AI浪潮改变世界。</p>
<p>目前我们也在寻找<strong>能够理解硬核技术创新、有全球化市场资源的投资机构</strong>，<strong>欢迎有投资意向的大咖垂询</strong>。</p>
<p><img src="https://filecdn.code2life.top/tensor-fusion.png" alt=""></p>
<h2 id="reference" tabindex="-1">Reference <a class="header-anchor" href="#reference" aria-label="Permalink to &quot;Reference&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li>Duato, José, et al. &quot;rCUDA: Reducing the number of GPU-based accelerators in high performance clusters.&quot; 2010 International Conference on High Performance Computing &amp; Simulation. IEEE, 2010.</li>
<li>Reaño, Carlos, and Federico Silla. &quot;Redesigning the rCUDA communication layer for a better adaptation to the underlying hardware.&quot; Concurrency and Computation: Practice and Experience 33.14 (2021): e5481.</li>
<li>Tobler, Lukas. Gpuless–serverless gpu functions. Diss. Master’s thesis. ETH, 2022.</li>
<li>Gu, Jing, et al. &quot;GaiaGPU: Sharing GPUs in container clouds.&quot; 2018 IEEE Intl Conf on Parallel &amp; Distributed Processing with Applications, Ubiquitous Computing &amp; Communications, Big Data &amp; Cloud Computing, Social Computing &amp; Networking, Sustainable Computing &amp; Communications (ISPA/IUCC/BDCloud/SocialCom/SustainCom). IEEE, 2018.</li>
<li>Song, Shengbo, et al. &quot;Gaia scheduler: A kubernetes-based scheduler framework.&quot; 2018 IEEE Intl Conf on Parallel &amp; Distributed Processing with Applications, Ubiquitous Computing &amp; Communications, Big Data &amp; Cloud Computing, Social Computing &amp; Networking, Sustainable Computing &amp; Communications (ISPA/IUCC/BDCloud/SocialCom/SustainCom). IEEE, 2018.</li>
<li>Liu, Zijie, et al. &quot;KubFBS: A fine‐grained and balance‐aware scheduling system for deep learning tasks based on kubernetes.&quot; Concurrency and Computation: Practice and Experience 34.11 (2022): e6836.</li>
<li>Yeh, Ting-An, Hung-Hsin Chen, and Jerry Chou. &quot;KubeShare: A framework to manage GPUs as first-class and shared resources in container cloud.&quot; Proceedings of the 29th international symposium on high-performance parallel and distributed computing. 2020.</li>
<li>Chen, Hung-Hsin, et al. &quot;Gemini: Enabling multi-tenant gpu sharing based on kernel burst estimation.&quot; IEEE Transactions on Cloud Computing 11.1 (2021): 854-867.</li>
<li>Lou, Jie, et al. &quot;ArkGPU: enabling applications’ high-goodput co-location execution on multitasking GPUs.&quot; CCF Transactions on High Performance Computing 5.3 (2023): 304-321.</li>
<li>Hong, Cheol-Ho, Ivor Spence, and Dimitrios S. Nikolopoulos. &quot;GPU virtualization and scheduling methods: A comprehensive survey.&quot; ACM Computing Surveys (CSUR) 50.3 (2017): 1-37.</li>
<li>A Closer Look at VirtIO and GPU Virtualisation | Blog | Linaro. www.linaro.org/blog/a-closer-look-at-virtio-and-gpu-virtualisation</li>
<li>Brief Introduction of GPU Virtualization | Blog | Aliyun. developer.aliyun.com/article/590916</li>
<li>Run TorchServe with Nvidia MPS | Blog | Github. github.com/pytorch/serve/blob/master/docs/nvidia_mps.md</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[SaaS的本质是什么]]></title>
            <link>https://code2life.top/blog/0079-what-is-saas</link>
            <guid>https://code2life.top/blog/0079-what-is-saas</guid>
            <pubDate>Fri, 23 Aug 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="saas的本质是什么" tabindex="-1">SaaS的本质是什么 <a class="header-anchor" href="#saas的本质是什么" aria-label="Permalink to &quot;SaaS的本质是什么&quot;">&ZeroWidthSpace;</a></h1>
<nav class="table-of-contents"><ul><li><a href="#引言">引言</a></li><li><a href="#什么时候买saas">什么时候买SaaS？</a></li><li><a href="#什么时候不买saas">什么时候不买SaaS?</a></li><li><a href="#saas价值如何产生">SaaS价值如何产生？</a></li><li><a href="#saas三支柱模型">SaaS三支柱模型</a></li><li><a href="#saas三支柱模型能解决什么问题">SaaS三支柱模型能解决什么问题？</a></li><li><a href="#总结">总结</a></li></ul></nav>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>年初跟一位做SaaS的企业家朋友聊天，讨论到国内和国外SaaS业态差异，引出了一些深层思考。</p>
<p>他说，国内的决策机制和市场环境下，出现了大量非标需求，导致<strong>垂类SaaS倒向了定制开发、私有化交付</strong>，很难做到<strong>一个中心的云上系统，服务大多数客户</strong>。</p>
<p>那么，以这种形态交付的软件服务，还能称之为SaaS吗？</p>
<p>最后这位朋友的结论是：只要客户不是一次性买断，而是<strong>持续给软件付费</strong>，就算SaaS。</p>
<p>相比于典型的中心云SaaS，哪怕<strong>半私有化交付</strong>的<strong>边际成本</strong>和<strong>获客成本</strong>更高，只要能一直收到钱，公司就能活下去，交付形式没有那么重要。</p>
<p><strong>SaaS的本质是订阅</strong>。</p>
<p>听了朋友的分析，我不禁感慨，<strong>实践派的观点总是简单而深刻</strong>，这篇文章我们从“<strong>SaaS的本质是订阅</strong>”的观点出发，从价值流角度，分析SaaS的本质是什么。</p>
<h2 id="什么时候买saas" tabindex="-1">什么时候买SaaS？ <a class="header-anchor" href="#什么时候买saas" aria-label="Permalink to &quot;什么时候买SaaS？&quot;">&ZeroWidthSpace;</a></h2>
<p>既然SaaS最普遍的特征是“为软件服务持续付费”，首先要问一个问题：<strong>为什么客户会持续付费呢</strong>？</p>
<p>在<a href="/blog/0075-enterprise-6-order-derivative.html">六阶导数解构企业经营</a>中提到了一个常识：<strong>财务结果是对用户价值的度量</strong>，也就是说，<strong>客户觉得值得买，企业才可能盈利</strong>。</p>
<p>继续追问：<strong>SaaS的客户到底购买的是哪些「价值」呢</strong>？</p>
<p>我访谈过十几位采购SaaS的决策者，问他们<strong>到底是哪些因素，让他们选择了SaaS</strong>，而不是自研系统、传统方法手工做、外包这些方式。</p>
<p>下面总结了3类答案，不同的角色排序不一样，但最终都因为这些原因选择了SaaS。</p>
<h4 id="_1-saas的综合成本更低" tabindex="-1">1. SaaS的综合成本更低 <a class="header-anchor" href="#_1-saas的综合成本更低" aria-label="Permalink to &quot;1. SaaS的综合成本更低&quot;">&ZeroWidthSpace;</a></h4>
<p>综合看SaaS的投入产出比，一定比“全域自研”和“土法炼钢”都要高，TCO（Total cost of ownership）也比其他方案低得多。</p>
<p>比如常见的ZOOM、Google GSuite、WPS这类协作办公类SaaS，许可证费用才每人每月十几块到几十块钱，相比于云化前的办公文件到处传、线下到处找工位，省下了巨量的沟通成本和管理成本。</p>
<p>另外，相比于<strong>自研</strong>或<strong>买断</strong>，SaaS的<strong>租赁模式</strong>对现金流压力更小，再综合考虑<strong>货币时间价值、买断后的利用率</strong>这些因素，显然SaaS是<strong>综合成本更低的选择</strong>。</p>
<p>一般老板拍板买SaaS时，最看重的是这一点。</p>
<h4 id="_2-saas的经验起点高、迭代快" tabindex="-1">2. SaaS的经验起点高、迭代快 <a class="header-anchor" href="#_2-saas的经验起点高、迭代快" aria-label="Permalink to &quot;2. SaaS的经验起点高、迭代快&quot;">&ZeroWidthSpace;</a></h4>
<p>成熟的SaaS，凝结了这个领域最核心的经验，包括：</p>
<ul>
<li>以模板形式提供的<strong>可复用的资源</strong></li>
<li>以功能模块形式提供的<strong>可复制的能力</strong></li>
</ul>
<p>SaaS相当于<strong>租赁</strong>了这个垂直领域最关键的<strong>资源</strong>和<strong>能力</strong>。还是拿在线办公举例，买了WPS VIP后，既可以参考各种优秀的Excel/Doc模板<strong>资源</strong>，又能用云同步、格式转换这些开箱即用的<strong>能力</strong>，简直是打工人的救星。</p>
<p>一些对新技术采纳比较激进的决策者，不仅看重SaaS带来的<strong>经验起点</strong>，还会看重<strong>经验增量</strong>。每次版本更新，能不能解决更多实际问题？那些中小型SaaS能瓜分到行业龙头的份额，靠的就是在细分领域<strong>精准、快速</strong>迭代产生的<strong>增量价值</strong>。</p>
<p>一般CIO或部门领导拍板买SaaS时，最看重的是这一点。</p>
<h4 id="_3-saas能转移经营风险" tabindex="-1">3. SaaS能转移经营风险 <a class="header-anchor" href="#_3-saas能转移经营风险" aria-label="Permalink to &quot;3. SaaS能转移经营风险&quot;">&ZeroWidthSpace;</a></h4>
<p>不管是自研还是买断，都意味着所有<strong>使用和维护的责任</strong>都是自己承担了。而购买SaaS，意味着系统的维护责任、数据的部分管理责任、功能的安全合规性，相当一部分由供应商承担了。</p>
<p>承担的责任变少了，风险自然就被分摊掉了。</p>
<p>这点不管是IaaS/PaaS/SaaS都是一样的，区别是<strong>越靠近业务侧</strong>，供应商<strong>分摊掉的的责任和风险越多</strong>，当然潜台词是订阅价格也越贵。</p>
<p>合规、法务、一些相对保守的部门，采购、采纳SaaS最看重的是这一点。</p>
<h2 id="什么时候不买saas" tabindex="-1">什么时候不买SaaS? <a class="header-anchor" href="#什么时候不买saas" aria-label="Permalink to &quot;什么时候不买SaaS?&quot;">&ZeroWidthSpace;</a></h2>
<p>访谈也得出了另一个问题的答案：<strong>什么时候不该买SaaS</strong>？</p>
<p>下面三种场景中，SaaS的投入产出比是不够的，因此一般会选择<strong>自研、买断、收购、外包</strong>这些“非SaaS模式”获得服务：</p>
<ol>
<li><strong>与核心业务领域强相关</strong>。既然是在核心领域，企业自己有领域专家，一般选择自建而非购买。</li>
<li><strong>业务发展到一定规模后的垂直整合</strong>。业务到达一定体量后，<strong>上游服务的垂直整合</strong>就会变成降低经营风险和成本的重要战略方向。但一般也只是整合业务上游中的高价值、高风险的部分，大而全的盲目整合、全域自研<strong>不可取</strong>。</li>
<li>市场上存在TCO更低、ROI更高的其他方案，比如基于开源或免费软件的方案、整个职能外包的方案等等。具体选择哪种模式呢？一般是<strong>越接近底层</strong>越有倾向采纳开源方案，比如Linux、Kubernetes；越处于<strong>价值链边缘</strong>越倾向于整个职能外包，比如企业内部IT服务、财务中的账务服务等等。</li>
</ol>
<h2 id="saas价值如何产生" tabindex="-1">SaaS价值如何产生？ <a class="header-anchor" href="#saas价值如何产生" aria-label="Permalink to &quot;SaaS价值如何产生？&quot;">&ZeroWidthSpace;</a></h2>
<p>搞清楚了“<strong>客户为什么觉得SaaS值得买</strong>”，我们再从SaaS公司内部组织结构做<strong>价值流分析</strong>，就离SaaS的本质更进一步了。</p>
<p>我们把一个SaaS企业价值链的关键部门，按照关联程度做一个聚类分析，可以找到以下三类组织。</p>
<ol>
<li>跟客户沟通的</li>
<li>提供服务的</li>
<li>设计和改善服务的</li>
</ol>
<h4 id="_1-跟客户沟通的" tabindex="-1">1. 跟客户沟通的 <a class="header-anchor" href="#_1-跟客户沟通的" aria-label="Permalink to &quot;1. 跟客户沟通的&quot;">&ZeroWidthSpace;</a></h4>
<p>这类组织冲在前线，大多数工作都是<strong>沟通</strong>。常见的组织形态有：</p>
<ul>
<li><strong>Sales</strong>：传递产品和服务的价值，促成客户的采购决策</li>
<li><strong>Solution Architect (SA)</strong>：根据客户的实际问题，提供定制化的完整售前方案</li>
<li><strong>Customer Success Manager (CSM)</strong>：全生命周期的客户体验管理</li>
<li><strong>Technical Account Manager (TAM)</strong>：协助用户管理数据、提升对技术的使用深度</li>
</ul>
<p>他们站在客户的立场上，作为客户的“<strong>业务伙伴</strong>”，发现客户面临的挑战，对外推动客户的购买和使用，对内反馈新问题和新需求。</p>
<p>这类组织的关键词是<strong>Discover</strong>。</p>
<h4 id="_2-提供服务的" tabindex="-1">2. 提供服务的 <a class="header-anchor" href="#_2-提供服务的" aria-label="Permalink to &quot;2. 提供服务的&quot;">&ZeroWidthSpace;</a></h4>
<p>SaaS用户购买产品后，并不是直接找上面这些角色提供服务。大多数情况下是<strong>软件自助服务</strong>和<strong>人工技术支持</strong>。常见的组织形态是：</p>
<ul>
<li><strong>SaaS产品本体</strong>。在不同领域衍生出各种”X“aaS, 载体可以是GUI Portal，或是没有GUI的SDK、API，甚至是内置在硬件内的系统</li>
<li><strong>Support Specialist/Service Engineer (SS/SE)</strong>：技术支持/服务工程师</li>
</ul>
<p>这些组织提供的服务，是<strong>用户拿在手里解决问题的“锤子”</strong>。不同于买个真锤子，SaaS是软件定义的、逻辑上的“虚拟锤子”，底层的物理资源实际上是共享的。因此，SaaS对<strong>计算资源</strong>、<strong>支持人员</strong>这些物理资源的<strong>调度效率越高</strong>，也就意味着<strong>服务共享程度越高</strong>、<strong>自助服务程度越高</strong>、<strong>交付服务的速度越快</strong>。</p>
<p>这类组织的关键词是<strong>Deliver</strong>。</p>
<h4 id="_3-设计和改善服务的" tabindex="-1">3. 设计和改善服务的 <a class="header-anchor" href="#_3-设计和改善服务的" aria-label="Permalink to &quot;3. 设计和改善服务的&quot;">&ZeroWidthSpace;</a></h4>
<p>有了<strong>第一类组织</strong>跟客户沟通，就有了源源不断的输入，这些输入<strong>汇集到一个领域专家组成的产品团队</strong>，不断升级和改善<strong>第二类组织</strong>所交付的服务。</p>
<p>这样，一个以客户为中心的飞轮模型就搭建完成了。在输入和输出之间，<strong>承担服务流程设计和改善责任的</strong>，就是<strong>第三类组织</strong>，常见形态是：</p>
<ul>
<li><strong>Product Manager</strong>：接收各方输入，<strong>抽象出</strong>解决一类相似<strong>具象问题</strong>的<strong>产品模型</strong>，用<strong>自然语言</strong>表达<strong>产品模型</strong>。</li>
<li><strong>Software Engineer</strong>: 构建软件模型，用<strong>编程语言</strong>表达出<strong>产品模型</strong>。</li>
<li><strong>UX Design</strong>：作为前两者的桥梁，站在用户体验角度，用<strong>设计语言</strong>表达出*<strong>产品模型</strong>。</li>
</ul>
<p>这三个角色都是在做模型设计，只是表达方式和视角不同。<strong>软件的设计等于生产</strong>，一个迭代周期结束，产出就是服务流程的改进结果。</p>
<p>这类组织上SaaS的决策中心、经验中心、变革中心，其综合水平，直接决定了产品水平。</p>
<p>因此，SaaS公司非常看重构建Engineering-Product-Design铁三角(EPD Triad)，把研发团队和产品团队视为核心资产。</p>
<p>这类组织的关键词是<strong>Design</strong>。</p>
<h2 id="saas三支柱模型" tabindex="-1">SaaS三支柱模型 <a class="header-anchor" href="#saas三支柱模型" aria-label="Permalink to &quot;SaaS三支柱模型&quot;">&ZeroWidthSpace;</a></h2>
<p>跟客户沟通的、提供服务的、设计和改善服务的，这三类角色的协同工作流，构成了一个创造价值的动态过程，即价值流。</p>
<p>借鉴人力资源管理的三支柱理论，我总结了一个<strong>SaaS的三支柱模型</strong>。</p>
<p><img src="https://filecdn.code2life.top/SaaS-3-Pillar.png" alt=""></p>
<ul>
<li>上面的一环是<strong>共享服务中心</strong> (Shared Service Center - SSC），以产品形式提供<strong>自助服务</strong>的入口和接口，聚焦Delivery。平台化做的比较成熟的也可以叫<strong>共享交付中心</strong>(Shared Delivery Center - SDC)。</li>
<li>下面左侧的一环是<strong>业务伙伴</strong> (Business Partner - BP), 是连接客户与软件服务商的桥梁、信息沟通的渠道，让客户不仅能<strong>把软件用起来，还能用的好、用的深</strong>。</li>
<li>下面右侧的一环是<strong>专家中心</strong> (Center of Expertise - CoE), 是SaaS冰山下的<strong>核心能力</strong>，不断吸收和抽象来自客户的、市场的、技术领域的输入，在团队中和软件中不断沉淀新的经验，不断提升软件服务的价值。</li>
</ul>
<p>按这个定义方式，任何一个SaaS公司的岗位，都能划分到三支柱中，比如：</p>
<ul>
<li>服务运维(DevOps)、Technical Writer团队属于<strong>SSC</strong></li>
<li>发布管理、TPM、QA等团队属于<strong>CoE</strong></li>
<li>Marketing、PR等团队属于<strong>BP</strong></li>
</ul>
<p>比较特殊的是 HR，Finance &amp; Accounting，Security、Legal 这些公司职能部门，他们本身就是一个具备完整三支柱的服务单元。其实<strong>三支柱模型</strong>理论就是先在<strong>人力资源、财务</strong>这些<strong>完整的服务部门</strong>发展应用起来的。</p>
<p>类比一个人，<strong>CoE是大脑、SSC是四肢、BP是五感</strong>。</p>
<p>类比一个细胞，<strong>CoE</strong>是控制整个细胞结构的<strong>细胞核</strong>，<strong>SSC</strong>是有各种功能的<strong>细胞质</strong>，<strong>BP</strong>是接受和释放调控信号分子的<strong>细胞膜</strong>。</p>
<h2 id="saas三支柱模型能解决什么问题" tabindex="-1">SaaS三支柱模型能解决什么问题？ <a class="header-anchor" href="#saas三支柱模型能解决什么问题" aria-label="Permalink to &quot;SaaS三支柱模型能解决什么问题？&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="_1-诊断服务质量问题" tabindex="-1">1. 诊断服务质量问题 <a class="header-anchor" href="#_1-诊断服务质量问题" aria-label="Permalink to &quot;1. 诊断服务质量问题&quot;">&ZeroWidthSpace;</a></h4>
<p>这种对SaaS的理解方式，最直接的作用是帮助我们发现哪里薄弱了、哪个角色缺位了，是做<strong>咨询诊断</strong>的好工具。</p>
<p>如果一个SaaS的服务质量有问题，从“问题在哪里”这个开放性问题出发，做好访谈、问卷、调查，就能逐步<strong>收敛</strong>到三支柱中的一些非常具体的因素。比如：是CSM管理了太多客户沟通不够、还是没有在工作中建立和CoE中PM团队的定期会议反馈问题？是产品团队的工程师能力问题开发速度太慢，还是因为没有重视用户反馈在闭门造车？是支持团队没有对产品有足够的理解解决不了用户的Ticket，还是产品本身有太多隐藏逻辑导致了用户和支持人员都没法自助服务？</p>
<p>举个我经历的例子，公司内部有十几个内部平台和中间件服务，但这些“内部SaaS”的成熟度不够、用户满意度不高，导致业务交付速度慢，市场竞争力难以提升。我们从去年年中开始，想办法解决这个问题。</p>
<p>一圈问卷调查和访谈下来，定位到主要问题在于<strong>BP和SSC两个支柱</strong>很薄弱，当时，60%以上的人认为没办法找到服务的支持人员、文档质量太差无法自助上手使用、服务不稳定等等。</p>
<p>再深入一层细节，看到是文档写了但很散乱、过时的比较多；支持人员是有排班的，但对人工支持的SLA管理没有闭环，没办法端到端解决用户问题等等，不展开了。</p>
<p>诊断完了，解决方案就有了。我们在SSC这一侧，构建了<strong>新的结构化的、集中管理的、AI自动问答的内部用户文档中心</strong>；在BP这一侧，构建专门的运营小组，做真正的SaaS服务的CSM、Solution Architect的事情，<strong>以服务为本，用数据说话</strong>。</p>
<p>一年多后，用户满意度已经有比较明显的提升。</p>
<p>这段经历让我理解了解决服务管理问题的朴素思路：对照着理论模型，搞清楚现实挑战，再收窄到几个主要问题，<strong>把缺的地方补上，掉在地上的事情捡起来</strong>。</p>
<h4 id="_2-构建高效的coe" tabindex="-1">2. 构建高效的CoE <a class="header-anchor" href="#_2-构建高效的coe" aria-label="Permalink to &quot;2. 构建高效的CoE&quot;">&ZeroWidthSpace;</a></h4>
<p>三支柱模型有个有意思的地方，<strong>在CoE的内部，竟然套娃了另一个三支柱模型</strong>。</p>
<p>上面我们提到组成软件产品研发团队最关键的三个岗位 Engineering-Product-Design，<strong>EPD-Triad 正好和SaaS服务整体三支柱相对应</strong>：</p>
<ul>
<li><strong>Engineering</strong>，代表了沉淀在软件里的领域经验，关注如何用软件模型本身的结构，<strong>是CoE中的CoE视角</strong>，</li>
<li><strong>Product</strong>，代表了从客户反馈提炼的产品迭代方向和目标，关注如何用产品帮助客户解决问题，<strong>是CoE中的BP视角</strong>，</li>
<li><strong>Design</strong>，代表了服务的用户交互流程，关注如何让自助服务的体验最优，<strong>是CoE中的SSC视角</strong>。</li>
</ul>
<p>世界是分形的。理解CoE内部的这种<strong>分形结构</strong>，对实际工作有什么意义呢？</p>
<p>我认为<strong>理解本身就是意义</strong>。</p>
<p><strong>理解了各方的立场，就能够促进各方高效协作</strong>。</p>
<p>很多软件公司经常遇到PM和工程师冲突、UX Design在前端实际用不了、工程师产出和实际需求脱节，这些都是没有正确理解CoE中这EPD三个角色的<strong>思维模式和分工意义</strong>导致的。</p>
<p>举两个常见的例子：有些工程师等着产品完全设计好才开始思考，只做一次把需求翻译成编程语言的工作，完全不关心用户目标、场景、满意度；有些交互设计师对业务领域了解很少，甚至没有完整用过产品，只是拿着PM的需求文档转换成设计稿。。</p>
<p>这两种情况都说明<strong>领域专家缺位</strong>，Designer停留在UI设计而非UX，Engineer停留在码代码而非解决工程问题，这种模式下，团队很容易产生冲突，产品质量一定不好。因为他们<strong>都没有真正成为领域专家的一员</strong>，只做了<strong>翻译的工作</strong>，没有用设计语言、编程语言做<strong>创造的工作</strong>。只把技术当一门手艺的人，在职业发展上是走不远的。</p>
<p>反之，<strong>真正理解了自己处在一个服务组织的CoE价值链之中，持续沟通、通过构建模型不断解决领域问题的，成为领域专家的一员，往往能走得很远</strong>。</p>
<h4 id="_3-设计有效的沟通架构" tabindex="-1">3. 设计有效的沟通架构 <a class="header-anchor" href="#_3-设计有效的沟通架构" aria-label="Permalink to &quot;3. 设计有效的沟通架构&quot;">&ZeroWidthSpace;</a></h4>
<p>沟通架构可能是公司治理中最重要的一环，三支柱模型可以指导新的服务部门或SaaS公司的顶层设计，因为 Discover-Design-Deliver三支柱之间的信息流转方向，就是沟通架构设计的最优解。</p>
<p>在软件行业有一个“康威定律”，本质上就是<strong>沟通架构决定产品架构</strong>，一旦沟通结构错了，产品一定会变形。</p>
<p>大公司病也是从沟通病开始的。基层员工<strong>缺少战略地图引导和跨职能沟通</strong>，长此以往便<strong>看不到价值链的全貌</strong>，只关注自己做什么，不关注大目标是什么。这种微观问题在宏观上的表现，就是目标和任务颠倒、部门墙出现、内部协作成本剧增、公司整体发展停滞。</p>
<p>比如，如果CoE中的PM，和BP中的CSM、SSP中的Support 没有常态化的双向沟通，那么产品迭代一定会出问题。CoE中的Engineer和SSC中的DevOps如果没有常态化双向沟通，服务的可用性和交付速度一定会出问题。</p>
<p>因此，管理者在辅导团队成员制定OKR的时候，一定要把在沟通架构中与之协作的角色纳入考虑，将双方或多方<strong>共同达成的目标</strong>列入OKR，而<strong>不仅是自己所在职能的单一目标</strong>，再让目标来引导出正确的沟通架构。</p>
<p>三支柱模型本身就是<strong>顶层的沟通架构</strong>，能够指导<strong>局部的管理实践</strong>，让每个局部之间的沟通架构有效运转。每一层的沟通架构对了，就能防治“康威定律的诅咒”和“大公司病”。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文从SaaS的持续订阅这个关键特征出发，分析客户为什么买SaaS，再分析一个SaaS企业的组织结构，推导出三支柱模型，最后讲了怎么用三支柱模型解决实际问题。</p>
<p>回到对SaaS本质的理解，我认为下面三个说法都对，只是看待概念的视角不同。</p>
<p>SaaS的本质，是订阅。</p>
<p>SaaS的本质，是一种以软件为中心的、<strong>CoE, BP, SSC</strong>三支柱共同组成的 Discover-Design-Deliver 业务模式。</p>
<p>SaaS的本质，是<strong>一种由领域专家构建的、持续迭代的、能够让用户自助操作的软件系统所提供的服务</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[如何写出高质量的技术文档?]]></title>
            <link>https://code2life.top/blog/0078-how-to-write-good-tech-docs</link>
            <guid>https://code2life.top/blog/0078-how-to-write-good-tech-docs</guid>
            <pubDate>Thu, 11 Jul 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="如何写出高质量的技术文档" tabindex="-1">如何写出高质量的技术文档? <a class="header-anchor" href="#如何写出高质量的技术文档" aria-label="Permalink to &quot;如何写出高质量的技术文档?&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h2>
<nav class="table-of-contents"><ul><li><a href="#目录">目录</a></li><li><a href="#好文档是核心竞争力">好文档是核心竞争力</a></li><li><a href="#怎么给文档分类">怎么给文档分类？</a><ul><li><a href="#以读者为中心的信息架构">以读者为中心的信息架构</a></li><li><a href="#技术文档的读者有哪些阅读目的">技术文档的读者有哪些阅读目的？</a></li><li><a href="#深入理解diataxis框架的四类文档">深入理解Diátaxis框架的四类文档</a></li></ul></li><li><a href="#怎么组织文档网站">怎么组织文档网站？</a><ul><li><a href="#首页放什么">首页放什么</a></li><li><a href="#在用户恰好要用时-让文档冒出来">在用户恰好要用时，让文档冒出来</a></li><li><a href="#哪些-文档-不该放">哪些“文档”不该放？</a></li></ul></li><li><a href="#写好技术文档的7条原则">写好技术文档的7条原则</a><ul><li><a href="#_1-面对面-原则">1. “面对面”原则</a></li><li><a href="#_2-零知识假设原则">2. 零知识假设原则</a></li><li><a href="#_3-金字塔原则">3. 金字塔原则</a></li><li><a href="#_4-海明威-原则">4. “海明威”原则</a></li><li><a href="#_5-结构化优先原则">5. 结构化优先原则</a></li><li><a href="#_6-多媒体优先原则">6. 多媒体优先原则</a></li><li><a href="#_7-迭代原则">7. 迭代原则</a></li></ul></li><li><a href="#高效写作工具箱">高效写作工具箱</a><ul><li><a href="#基础写作套件">基础写作套件</a></li><li><a href="#ai工具箱">AI工具箱</a></li><li><a href="#模板库和参考示例">模板库和参考示例</a></li></ul></li><li><a href="#总结">总结</a></li></ul></nav>
<h2 id="好文档是核心竞争力" tabindex="-1">好文档是核心竞争力 <a class="header-anchor" href="#好文档是核心竞争力" aria-label="Permalink to &quot;好文档是核心竞争力&quot;">&ZeroWidthSpace;</a></h2>
<p>对用户来说，文档是产品必不可少的一部分，是<strong>使用和信任技术产品</strong>的前提。</p>
<p>对团队来说，<strong>文档是最有效的沟通方式</strong>。聊天、会议的<strong>内容会被忘记、内容深度和时效性都无法保障</strong>，而<strong>文档能突破时间和空间的限制</strong>保存知识。</p>
<p>对个人来说，<strong>文档是能力成长的必经路</strong>。写文档是锻炼<strong>结构化思维、视角转换能力</strong>最有效的办法。见字如面，判断一个做技术的人是否靠谱，我会重点看<strong>写了什么，而不仅是说了什么</strong>。</p>
<p>关于“<strong>怎么写好技术文档</strong>”的文章太多了，这里分享一些不一样的干货：<strong>一个理论、7条原则、21条行动建议</strong>。</p>
<h2 id="怎么给文档分类" tabindex="-1">怎么给文档分类？ <a class="header-anchor" href="#怎么给文档分类" aria-label="Permalink to &quot;怎么给文档分类？&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="以读者为中心的信息架构" tabindex="-1">以读者为中心的信息架构 <a class="header-anchor" href="#以读者为中心的信息架构" aria-label="Permalink to &quot;以读者为中心的信息架构&quot;">&ZeroWidthSpace;</a></h3>
<p>技术文档往往既<strong>专业又复杂</strong>，要呈现的东西太多。因此，第一步是理清<strong>信息架构</strong>。</p>
<p>很多人思路是，“<strong>我有什么就写什么</strong>”，要么按功能分类，把用户手册平铺下去，要么只有自动生成的SDK/API文档。</p>
<p>用户读这类“以产品为中心”堆砌功能的文档，就像去一个地方旅游没有路线图，像无头苍蝇一样到处跑，<strong>该去哪里、怎么去、还有哪些没去</strong>，全然不知。</p>
<p>我们一定要换到<strong>文档的用户--读者</strong>视角思考，想清楚<strong>用户的使用场景是什么，阅读文档的目的是什么</strong>。</p>
<p><strong>按使用场景</strong>设计文档的分类维度和信息架构，就像给了用户“旅游路线图”，爬山线路走这边、休闲路走那边，用户想要的自然一眼就能找到。</p>
<p>🚀 行动建议1：写任何文档之前，先画一个脑图梳理<strong>读者有哪些目标</strong>，把这张脑图贯穿整个写作过程。</p>
<h3 id="技术文档的读者有哪些阅读目的" tabindex="-1">技术文档的读者有哪些阅读目的？ <a class="header-anchor" href="#技术文档的读者有哪些阅读目的" aria-label="Permalink to &quot;技术文档的读者有哪些阅读目的？&quot;">&ZeroWidthSpace;</a></h3>
<p>技术文档一般有哪些使用场景呢？我看到过最好的答案是<a href="https://diataxis.fr" target="_blank" rel="noreferrer">Diátaxis框架</a>的分类方法。</p>
<p>Diátaxis框架是工程师Daniele Procida总结的<strong>文档方法论</strong>，很多知名项目和公司已经在用了，比如<a href="https://ubuntu.com/blog/diataxis-a-new-foundation-for-canonical-documentation" target="_blank" rel="noreferrer">Ubuntu</a>、<a href="https://discuss.python.org/t/adopting-the-diataxis-framework-for-python-documentation/15072/51" target="_blank" rel="noreferrer">Python</a>、<a href="https://github.com/evildmp/diataxis-documentation-framework/blob/main/index.rst" target="_blank" rel="noreferrer">Cloudflare</a>、Microsoft、Gatsby、HuggingFace等等。</p>
<p>Diátaxis框架按 <strong>认知形式</strong> 和 <strong>采纳程度</strong> 两个维度，把文档分成了四类。</p>
<p><img src="https://filecdn.code2life.top/imgs/diataxis.webp" alt=""></p>
<p>纵轴是按<strong>主要认知形式</strong>分的 <strong>知（Cognition）、行（Action）</strong>。越下面的越偏向<strong>认知为主</strong>，越上面的越偏向<strong>行动为主</strong>。一般情况下，技术文档要<strong>优先呈现行动的部分，而不是理论的部分</strong>。打个比方，对于厨师，看食谱比看烹饪理论管用的多；介绍食谱的书，不需要解释烹饪理论；烹饪理论的书，也不应该把菜具体怎么做一步一步列出来。</p>
<p>🚀行动建议2：偏实践和偏理论的文档分开写，按照 先“实”后“理”，或者说先“行”后“知”的顺序，呈现给用户。</p>
<p>横轴是按<strong>采纳程度</strong>分的 <strong>学（Acquisition）、 用(Application)</strong>。左侧是小白用户的<strong>教科书</strong>，右侧是专家用户的<strong>参考书</strong>。</p>
<p>在<strong>学的阶段</strong>，文档组织方式是<strong>师生模型</strong>，有<strong>学习的目标、难度、路径、进度</strong>，注重的是兴趣和动机、评估和反馈。</p>
<p>到<strong>用的阶段</strong>，文档应该是<strong>任务模型</strong>，以<strong>解决实际问题驱动</strong>，注重<strong>实用性和效率</strong>，多给一些<strong>拿来就用的例子</strong>，让用户在实际工作中快速查找和使用信息。</p>
<h3 id="深入理解diataxis框架的四类文档" tabindex="-1">深入理解Diátaxis框架的四类文档 <a class="header-anchor" href="#深入理解diataxis框架的四类文档" aria-label="Permalink to &quot;深入理解Diátaxis框架的四类文档&quot;">&ZeroWidthSpace;</a></h3>
<p>有了两个坐标轴分出来的<strong>知、行、学、用</strong>作为标尺，四个象限就出来了，对应了四个阅读场景、四种文档类型。</p>
<p>我们先从用户群体的角度看：</p>
<ul>
<li><strong>左上角：Tutorial</strong> --<strong>入门教程</strong>。适合<strong>小白用户</strong>过来体验一下。通过具体行动获得感受，才能进入更深的认知层面。</li>
<li><strong>左下角：Explanation</strong> -- <strong>解释材料</strong>。适合刚跟着Tutorial简单用了一遍，但满脑子问号的<strong>入门用户</strong>。&quot;这些概念是什么意思？为什么这样设计？怎么工作的？&quot;回答了这些问题，用户就理解了产品的<strong>基本原理和核心概念</strong>。一些给专家用户学习的<strong>高级概念</strong>也可以放进来，但一定要和<strong>基本概念</strong>分开。</li>
<li><strong>右上角：How-to Guides</strong> -- <strong>使用指南</strong>。适合有实际使用经验的<strong>普通用户</strong>。有些项目的How-to Guides在文档目录也叫“Recipe”，就像在家做个便饭，看看食谱就能实现目标了。不需要精确理解概念和原理。</li>
<li><strong>右下角：Reference</strong> -- <strong>参考资料</strong>。适合给<strong>专业用户</strong>查阅高级功能的细节。打个比方，餐厅主厨做佛跳墙，可能也要去查查烹饪参考书。Reference是一种<strong>无聊但有用</strong>的文档。</li>
</ul>
<p>为了方便记忆，我们用<strong>武学境界</strong>来关联记忆这四类用户、四种文档：</p>
<p><img src="https://filecdn.code2life.top/imgs/diataxis-users.png" alt=""></p>
<p>🚀行动建议3：写每一篇文档之前，先回答3个问题来确定属于哪个Diátaxis象限：是给什么熟练度的读者看的？是重点介绍<strong>原理和概念</strong>，还是介绍<strong>一步一步怎么做</strong>？是用<strong>师生模型</strong>还是<strong>任务模型</strong>组织内容？</p>
<p>再换个角度，从<strong>修改频率</strong>来看，这四类文档的<strong>维护频率是越往右边越高</strong>。</p>
<p>左半边是Tutorial和Explanation，<strong>学习教程和概念解释不会很快变化</strong>，只有大模块发布、产品大范围重构的时候，才需要更新这两类文档。</p>
<p>🚀行动建议4：Tutorial、Explanation在季度或更长周期的<strong>大版本发布前</strong>写好。但要定期验证文档有效性，发现有问题随时小修小补。</p>
<p>而右半边的How-to Guides和Reference，是要<strong>跟着项目迭代随时维护的</strong>。How-to Guides相比Reference维护频率更低一些，因为要让用户有一个唯一正确的完成某项任务的方法，所以How-to Guides不用像Reference一样多版本并存。</p>
<p>🚀行动建议5：How-to Guides在每次产品发布前更新一轮，保持在每月一次的频率，确保新功能发布前<strong>先写了文档，然后再把这些文档总结成Release Notes</strong>，Release Notes内部附上涉及的How-to Guides链接。</p>
<p>而右下角的Reference文档，要有极高的<strong>准确性、时效性</strong>，因此维护频率最高，每个版本都需要一份，也不应该人工写。</p>
<p>🚀行动建议6：Reference文档由 源码 + Swagger、TypeDoc、Swim这类生成工具 + CI/CD Pipeline 自动生成，每个产品发布周期，都自动增加一个版本，按需留一定数量的历史版本。</p>
<h2 id="怎么组织文档网站" tabindex="-1">怎么组织文档网站？ <a class="header-anchor" href="#怎么组织文档网站" aria-label="Permalink to &quot;怎么组织文档网站？&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="首页放什么" tabindex="-1">首页放什么 <a class="header-anchor" href="#首页放什么" aria-label="Permalink to &quot;首页放什么&quot;">&ZeroWidthSpace;</a></h3>
<p>学习完Diátaxis框架后，我们有了四条彼此关联的“游览路线”，但这还不够，我们还要飞到高空，给所有文档拍一个鸟瞰图，制作成Overview页面，作为文档站首页。</p>
<p>Overview既能让用户<strong>快速获得全局理解</strong>，又能作为<strong>关键索引</strong>，让用户<strong>一眼找到想看的内容</strong>。</p>
<ul>
<li>Tutorial 拍个鸟瞰图，就是<strong>核心业务场景</strong>的<strong>Quick Start</strong>，以及其他Tutorial的索引列表</li>
<li>Explanation 拍个鸟瞰图，就是3W(What/Why/How it works), 一般文档标题是Introduction、Essential Concepts。另外，这里也要放一个“When to use/When not to use”的决策树，让用户快速判断这个产品能不能解决Ta的问题</li>
<li>How-to Guides 在鸟瞰图中，浓缩成了一个<strong>关键任务指南的索引</strong>，可以叫“Recipes”</li>
<li>Reference 太过细节了，在鸟瞰图中就要放链接就足够了，不必展开。</li>
</ul>
<p>此外，首页还肩负<strong>快速打消用户顾虑</strong>的任务，因此，下面这些文档和资源链接<strong>一定要放进去</strong>：</p>
<ul>
<li><strong>FAQ</strong>，为了防止遗漏了用户最关心的问题，首页要加一个<strong>15个问题以内</strong>的FAQ</li>
<li><strong>用户信任</strong>相关的资源链接，用全套专业材料证明这是一个靠谱的技术产品：
<ul>
<li>稳定性相关的：比如可用性指标，Status Page、服务水平协议(SLA)</li>
<li>安全合规相关的：安全白皮书、合规相关证明、SBOM和许可证清单</li>
<li>性能相关的：Benchmark测试结果，最好是和竞品对比的</li>
</ul>
</li>
</ul>
<p>🚀行动建议7：在<a href="https://docs.code2life.top/guide/overview" target="_blank" rel="noreferrer">这份Overview提纲</a>的基础上，调整成你的文档首页。</p>
<h3 id="在用户恰好要用时-让文档冒出来" tabindex="-1">在用户恰好要用时，让文档冒出来 <a class="header-anchor" href="#在用户恰好要用时-让文档冒出来" aria-label="Permalink to &quot;在用户恰好要用时，让文档冒出来&quot;">&ZeroWidthSpace;</a></h3>
<p>有了独立的文档网站，但对用户来说还不够。以用户为中心的文档设计，还要尽量避免用户的上下文切换。因此，应该尽量用<strong>推送</strong>而非<strong>拉取</strong>的方式，<strong>在恰好的时机，把用户刚好需要的文档</strong>呈现出来。</p>
<p>有不少B2B的复杂SaaS产品，比如Permit.io、Cloudflare，都大量运用了<strong>交互式文档</strong>，在把<strong>文档融入产品功能中，让文档找用户，而不是用户找文档</strong>。</p>
<p><img src="https://filecdn.code2life.top/imgs/permit-io-doc-as-func.png" alt="permit.io"></p>
<p>🚀行动建议8：对于<strong>学习曲线较平的功能</strong>，把<strong>介绍视频、富文本内容直接做到功能中</strong>，用<a href="https://ant-design.antgroup.com/components/tour-cn" target="_blank" rel="noreferrer">漫游式引导</a>(Product Tour)放到产品中，吸引用户注意力，让用户自行探索。</p>
<p>除了<strong>文档功能化</strong>，另一个方案是开发Playground，作为产品的一部分。Playground或者更复杂一些的Hands-on Lab，不仅可以作为产品<strong>端到端测试的载体</strong>，更大的意义是<strong>提升用户尝试的信心、降低学习门槛</strong>。比如<a href="https://workers.cloudflare.com/playground" target="_blank" rel="noreferrer">Cloudflare Workers Playground</a>，体验过的都会被震惊到。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_fc7f2fe816097efefb700e26da52e8cc66f082eee1b8747024602853b69ddfef.png" alt=""></p>
<p>🚀行动建议9：对于<strong>学习曲线较陡、或操作风险较高的功能</strong>，尽量把Tutorial内容维护在可交互的Playground中，并在产品内加上Playground链接。切记<strong>不要重复造轮子</strong>，目前最简洁的前端内嵌Playground方案是<a href="https://github.com/codesandbox/sandpack" target="_blank" rel="noreferrer">CodeSandbox Sandpack</a>；如果场景更复杂，需要独立VM的Playground，买个<a href="https://instruqt.com/" target="_blank" rel="noreferrer">Instruqt</a>这种专门做Hands-on lab的服务；甚至还可以搭配<a href="https://www.skilljar.com" target="_blank" rel="noreferrer">Skill Jar</a>这类TMS产品，构建一个在线的“XX学院”，能跟踪用户的学习进度、还能用证书奖励促进学习动机。</p>
<h3 id="哪些-文档-不该放" tabindex="-1">哪些“文档”不该放？ <a class="header-anchor" href="#哪些-文档-不该放" aria-label="Permalink to &quot;哪些“文档”不该放？&quot;">&ZeroWidthSpace;</a></h3>
<p>除了Diátaxis框架提到的文档类型，我们还经常写一些其他“文档”，比如：<strong>Roadmap、ReleaseNotes、Newsletter、Ecosystem/Integrations</strong>。很多人也把这些“文档”放进产品文档了，而这些其实都不该放。</p>
<p>为什么呢？我们一个一个看。</p>
<p>我们仍然从目的论出发，想想<strong>Roadmap、Release Notes、Newsletter</strong>的目的和阅读场景是什么？这三类内容，都是发给那些对产品感兴趣的人的“信件”、“公告”，是<strong>结构化沟通的渠道</strong>。</p>
<p>Roadmap是为了提升产品研发的透明度、给用户适当的预期、甚至让用户投票参与决策；Release Notes是为了让用户了解和体验新功能；Newsletter则兼具维持透明度和营销两种目的。</p>
<p>可以发现，这类内容不涉及技术细节，但对<strong>内容吸引力、订阅群体管理、用户反馈</strong>的要求非常高。因此，要有一个专门的SaaS来管理这些内容，提供<strong>用户管理、内容营销</strong>的工作流闭环，这几种内容的深层含义，远比“定期写个文档丢到网站上”要复杂。</p>
<p>🚀行动建议10：不要把Roadmap和Release Notes放到文档站点中，它们不是技术文档，而是一类需要<strong>独立解决的客户沟通问题</strong>，市面上有很多产品可以选，比如：<a href="https://canny.io/" target="_blank" rel="noreferrer">Canny</a>、<a href="https://changelogfy.com/" target="_blank" rel="noreferrer">Changelogfy</a>、<a href="https://www.releasenotes.io/" target="_blank" rel="noreferrer">ReleaseNotes.io</a>、<a href="https://featureos.app/" target="_blank" rel="noreferrer">FeatureOS</a>。但有个特例是，非兼容版本更新产生的<strong>Migration Guides</strong>，属于<strong>How-to Guides</strong>的一种，应该放到文档站点。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_d4429d6b5ab02917d2508b97b7f2224c7d23faeabc32db2bc137aedf252417e9.png" alt=""></p>
<p>而Ecosystem/Integrations这些文档内容，背后的含义是什么呢？</p>
<p>一般<strong>扩展性比较好的技术产品、尤其是平台型产品</strong>，会留足扩展点，让<strong>用户参与开发，共同构建生态</strong>。这些用户贡献的Integrations，也要有使用文档，那这些内容放哪里呢？</p>
<p>简单的方案是，每当生态中增加一个Integration，就手动维护到主文档站点里面。但这样没有解耦<strong>产品维护者</strong>和<strong>生态贡献者</strong>，不符合关注点分离原则。因此，更好的做法是，构建一个Marketplace的数据库管理Integrations，扩展点开发者把<strong>文档和代码打包到一起</strong>，每次修改发布到Marketplace。</p>
<p>下面两张图，左边是Prometheus的Integrations、右边是Sanity的Integrations，哪个更好一目了然。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_ae13f8059d4daef14df070bd74a447dc2ee36999ec255648fe70fbb143303a2d.png" alt=""></p>
<p>🚀行动建议11：除了产品的技术文档本身，其他内容一概不应该在文档中维护。找一个类似<a href="https://sanity.io" target="_blank" rel="noreferrer">Sanity</a>的<strong>结构化CMS</strong>来管理Integrations、Teams、Products这些内容，不要直接放到产品文档里，这些都应该是Header或Footer中的资源链接。</p>
<h2 id="写好技术文档的7条原则" tabindex="-1">写好技术文档的7条原则 <a class="header-anchor" href="#写好技术文档的7条原则" aria-label="Permalink to &quot;写好技术文档的7条原则&quot;">&ZeroWidthSpace;</a></h2>
<p>有了对信息架构的理解，我们再看具体怎么写。这里介绍7条<strong>技术文档的写作原则</strong>，一部分内容来自布兰德·罗伊尔的《一本小小的红色写作书》。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_dd7bb4697753bd07119069b6aa96a0ea5dc7e694366ca7b489ba7255d362a8bf.png" alt=""></p>
<p>这里取其精华、再结合我的实践和延伸思考，总结了7条技术文档写作的核心原则，以及相应的行动建议。</p>
<ol>
<li>“面对面”原则</li>
<li>零知识假设原则</li>
<li>金字塔原则</li>
<li>“海明威”原则</li>
<li>结构化优先原则</li>
<li>多媒体优先原则</li>
<li>迭代原则</li>
</ol>
<h3 id="_1-面对面-原则" tabindex="-1">1. “面对面”原则 <a class="header-anchor" href="#_1-面对面-原则" aria-label="Permalink to &quot;1. “面对面”原则&quot;">&ZeroWidthSpace;</a></h3>
<p>“面对面”原则，就是时时刻刻想象你的对面，坐着一群读者，读者们有不同的角色，你在和他们<strong>一个一个的、有目的地沟通</strong>。有了这种<strong>对象感</strong>，会对技术有更本质的思考，你开发的技术产品，<strong>用户价值是什么？在更大的价值链中的定位是什么</strong>？。</p>
<p>这也是培养“以终为始”思维的一种方式，和亚马逊创始人贝佐斯提出的“逆向工作法”类似，先<strong>从“解决什么问题”出发、从FAQ开始构建一个产品</strong>。</p>
<p>🚀行动建议12：买几只小黄鸭放在屏幕旁边，想象他们是就是你的用户，和Ta默默对话，然后把“对话内容”转成书面语，一定是一篇好文档。和小黄鸭Debug法类似，小黄鸭写作法也有奇效。</p>
<h3 id="_2-零知识假设原则" tabindex="-1">2. 零知识假设原则 <a class="header-anchor" href="#_2-零知识假设原则" aria-label="Permalink to &quot;2. 零知识假设原则&quot;">&ZeroWidthSpace;</a></h3>
<p>借用社会学概念，零知识假设原则也可以叫<strong>无知之幕</strong>。用在写文档方面，就是假设你对面坐的读者，对这个产品一无所知。因此每写一个名词、动词，都要掂量一下Ta能不能理解。<strong>有没有上下文的解释？概念会不会被曲解？抽象程度是不是过高了不易理解？</strong></p>
<p>🚀行动建议13：找一个没有任何知识背景的小白来读文档，让Ta告诉你哪里看不懂。换位思考能力比较强的，可以把自己想象成这个小白。对于潜在的看不懂的地方，分成<strong>上文、下文</strong>两部分，对于上文，找到解释材料，放到当前文档的“Prerequisites/Background”中；对于下文，把相关文档的链接放到末尾的“Next Steps”中。</p>
<h3 id="_3-金字塔原则" tabindex="-1">3. 金字塔原则 <a class="header-anchor" href="#_3-金字塔原则" aria-label="Permalink to &quot;3. 金字塔原则&quot;">&ZeroWidthSpace;</a></h3>
<p>金字塔原则，简单理解是“结论先行”，这是《金字塔原理》的核心论点：一定要把<strong>关键结论放前面，解释和支持结论的论点放后面</strong>，<strong>自下而上思考、自上而下表达</strong>。</p>
<p>其实这篇文章就遵循了金字塔原则，开篇提出文档的重要性后，立刻总结了全文：一个理论、7条原则、21条行动建议，再展开阐述每个部分。</p>
<p>🚀行动建议14：以TL;DR（Too long, don't read）风格开头，先写塔尖的全文总结、再往塔底下展开详细内容，确保<strong>只读了第一段的用户，也能了解全文大意</strong>。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_fe204159b9381bd618d7920cbb95ace6f3c40c33a6dda3a40e2f77858b6944dc.png" alt=""></p>
<h3 id="_4-海明威-原则" tabindex="-1">4. “海明威”原则 <a class="header-anchor" href="#_4-海明威-原则" aria-label="Permalink to &quot;4. “海明威”原则&quot;">&ZeroWidthSpace;</a></h3>
<p>作家海明威的文风<strong>简洁明快、干净利落</strong>。商务写作、技术写作非常适合用“海明威体”。那么，怎么训练这种文风呢？有3个关键点：</p>
<ul>
<li>避免长句</li>
<li>少用副词，多用精确的动词</li>
<li>主动语态</li>
</ul>
<p>🚀行动建议15：用下面这段“海明威体AI提示词”，跟着AI老师训练一段时间，自己的文风就会干练很多。</p>
<div class="language-txt vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>You are professional tech docs writer, think Hemingway writing style and high quality tech doc factors, carefully review and modify the content, to align with Hemingway style and other writing best practices, fix spelling and grammar issues at the same time.</span></span>
<span class="line"><span>Input is markdown. Output should be 4 parts:</span></span>
<span class="line"><span>1. Overall score(0-100) of original content</span></span>
<span class="line"><span>2. The changed sentences diff</span></span>
<span class="line"><span>3. Final modified markdown</span></span>
<span class="line"><span>4. Overall score(0-100) of modified contents.</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h3 id="_5-结构化优先原则" tabindex="-1">5. 结构化优先原则 <a class="header-anchor" href="#_5-结构化优先原则" aria-label="Permalink to &quot;5. 结构化优先原则&quot;">&ZeroWidthSpace;</a></h3>
<p><strong>列表、表格、图表</strong>这些形式，都是结构化内容的载体，多多益善。结构化的背后是深思熟虑、用强空间结构表达关键论点和问题本质。这篇文章就是强列表体的体现。</p>
<p>这三种形式有什么区别，在什么场景用呢？</p>
<ul>
<li>单维度并列关系的信息、树状结构的信息，用列表体；</li>
<li>复杂的多维度信息，别用大片文字描述，直接上表格；</li>
<li>用来支持结论的分析类信息、尤其是数字、矩阵、时序相关的，选择恰当的图表。</li>
</ul>
<p>🚀行动建议16：找一些写过的文档，尝试把<strong>三分之一以上</strong>的内容全部改成列表、表格、图表。Markdown图表表格语法不熟练的话，用<a href="https://mermaid.live/" target="_blank" rel="noreferrer">Mermaid</a>、<a href="http://tableconvert.com/markdown-to-markdown" target="_blank" rel="noreferrer">TableConvert</a> 这类工具先练一练。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_b8b1d74ffef86368d6b0774b1ce1eeec66af33b9667153eb41f8793209a9f823.png" alt=""></p>
<h3 id="_6-多媒体优先原则" tabindex="-1">6. 多媒体优先原则 <a class="header-anchor" href="#_6-多媒体优先原则" aria-label="Permalink to &quot;6. 多媒体优先原则&quot;">&ZeroWidthSpace;</a></h3>
<p>多媒体可以调用<strong>多个感官，让用户进入多模态学习状态，是快速构建认知的捷径</strong>。有条件制作多媒体内容的，优先展示视频、动画这些内容形式。</p>
<p>另外，在文档中穿插一些紧扣主题的<strong>截屏、图片</strong>，也能帮助用户更快理解复杂的技术概念。</p>
<p>🚀行动建议17：用<a href="https://www.zoom.com" target="_blank" rel="noreferrer">ZOOM</a>、<a href="https://www.loom.com/" target="_blank" rel="noreferrer">Loom</a>、<a href="https://getkap.co/" target="_blank" rel="noreferrer">Kap</a> 、<a href="https://asciinema.org/" target="_blank" rel="noreferrer">Asciinema</a>这些能录制屏幕或命令行的工具，搭配<a href="https://www.capcut.cn/" target="_blank" rel="noreferrer">CapCut</a>剪辑配音工具，把最关键的几个教程，制作成<strong>3分钟以内的演示视频</strong>, 封面直接用主题大字，放到对应文档的开头。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_82a64e012845fa7368e21edbdafe9a444e13d8646211fdc6f208fdf883e0d2e1.png" alt=""></p>
<h3 id="_7-迭代原则" tabindex="-1">7. 迭代原则 <a class="header-anchor" href="#_7-迭代原则" aria-label="Permalink to &quot;7. 迭代原则&quot;">&ZeroWidthSpace;</a></h3>
<p>迭代原则有两层含义，一是发布前<strong>反复审阅修改</strong>，二是在<strong>整个产品生命周期保持文档更新</strong>。</p>
<p>科普作家游时猷曾说：“<strong>写文要快，改文要慢，查资料要适可而止</strong>。” 技术文档专业性、准确性、易用性要求都比较高，每篇文档至少要复审、迭代3遍才能发布。</p>
<p>除了发布版本的多次迭代，<strong>长期持续迭代一样重要</strong>。Diátaxis推崇<strong>随时随地，自内而外、自下而上</strong>逐步改进文档的方法，就像植物生长一样。如果不长期投入精力，随着产品本身的迭代，文档一定会逐渐腐化，失去生命力。</p>
<p><img src="https://filecdn.code2life.top/doc-plant.jpeg" alt=""></p>
<blockquote>
<p>Consider a plant. As a living, growing organism, a plant is never finished. -- Diátaxis</p>
</blockquote>
<p>🚀行动建议18：给文档站点加上评论、点赞等反馈渠道。每当收到用户反馈的通知时、或者自己阅读发现问题时，把要改的地方记到当日的Todo，尽量在当周内改完。</p>
<p>除了随时随地的维护，搭配<strong>周期性的检查、测试</strong>才能形成文档管理的闭环。文档负责人打好节拍，定期组织文档Review，一起修补、互相测试文档。</p>
<p>🚀行动建议19：每季度或者半年举办“Docathon”活动，专门用来测试、审核、改进现有的文档。设一些小奖品，奖励发现问题最多的人、改进文档最多的人。</p>
<h2 id="高效写作工具箱" tabindex="-1">高效写作工具箱 <a class="header-anchor" href="#高效写作工具箱" aria-label="Permalink to &quot;高效写作工具箱&quot;">&ZeroWidthSpace;</a></h2>
<p>理解了方法论和写作原则，在真正写技术文档之前，还要看下有没有配备一套<strong>趁手的工具箱</strong>。这里介绍三类提效工具：</p>
<ol>
<li>基础写作套件</li>
<li>AI工具箱</li>
<li>模板库和示例</li>
</ol>
<h3 id="基础写作套件" tabindex="-1">基础写作套件 <a class="header-anchor" href="#基础写作套件" aria-label="Permalink to &quot;基础写作套件&quot;">&ZeroWidthSpace;</a></h3>
<p>写作格式方面，Markdown是不二之选，功能全面、使用简单、生态丰富。我所有的文档、文章都是Markdown语法写的。</p>
<p>Markdown编辑器，IDE自带的已经足够了，我也试过各种花里胡哨的Markdown编辑器，最终回归了直接写Markdown语法的朴素方式。</p>
<p>写作平台方面，对于于技术人员来说，<strong>IDE first</strong>是效率最高的方式，没有什么是VSCode+一揽子插件搞不定的，有几个常用的插件：</p>
<ul>
<li><a href="https://mermaid.live/" target="_blank" rel="noreferrer">Mermaid</a>，代码画图工具，<a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one" target="_blank" rel="noreferrer">Markdown All in One插件</a> 已经内置支持</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=TakumiI.markdowntable" target="_blank" rel="noreferrer">Markdown Table</a>，辅助表格编辑和格式化。</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker" target="_blank" rel="noreferrer">Spell Checker</a> 单词拼写检查</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=adpyke.codesnap" target="_blank" rel="noreferrer">Code Snap</a> 代码片段截图</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image" target="_blank" rel="noreferrer">Markdown Image</a> 粘贴图片自动上传图床</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=knighd.imagemin" target="_blank" rel="noreferrer">ImageMin</a> 图片压缩</li>
</ul>
<p>IDE first最大的优势是，哪怕是复杂到要在文档中穿插交互组件时，配上<a href="https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx" target="_blank" rel="noreferrer">MDX</a>也能在IDE中轻松解决。结合Git自动化流水线，<strong>写作、发布全程甚至不用点一次鼠标</strong>。</p>
<p>辅助写作的工具方面，<a href="https://wantwords.net/" target="_blank" rel="noreferrer">WantWords反向词典</a>是一个好用的词汇工具、<a href="https://marketplace.visualstudio.com/items?itemName=funkyremi.vscode-google-translate&amp;ssr=false#overview" target="_blank" rel="noreferrer">VSCode Google Translate</a>能Alt+Shift+T一键翻译。剩下的辅助写作需求，都交给AI + Prompt Engineering吧。</p>
<h3 id="ai工具箱" tabindex="-1">AI工具箱 <a class="header-anchor" href="#ai工具箱" aria-label="Permalink to &quot;AI工具箱&quot;">&ZeroWidthSpace;</a></h3>
<p>技术文档有复杂的产品业务背景，不能全部交给AI生成。人工和AI互相配合，效率和质量都能有一些提升。</p>
<p>写作是人类最基础的表达能力，借力AI，不断精进写作能力才是正道，驾驭好AI工具非常关键。</p>
<p>现在市面上有两类AI产品，一类是基于RAG（增强检索生成）偏向语义搜索的AI；另一类是直接用大模型生成内容的AI。我的经验是<strong>取长补短，结合两类工具一起用</strong>。</p>
<ul>
<li>用ChatGPT、Kimi、Gemini、Claude这类<strong>纯生成类AI</strong>，用来协助构建整体结构的脑图、修改语法和拼写错误、自动翻译多语言</li>
<li>用 <a href="https://www.perplexity.ai/" target="_blank" rel="noreferrer">Peplexity AI</a>、<a href="https://metaso.cn/" target="_blank" rel="noreferrer">秘塔搜</a> 这类<strong>基于RAG的AI</strong>，协助引用专业资料，生成高质量的片段</li>
<li>在写完整个文档网站后，有条件尽量配置一个语义搜索的AI，市面上有大量开源和商业产品可以选，比如 <a href="https://www.algolia.com" target="_blank" rel="noreferrer">Algolia</a>、<a href="https://meilisearch.com/" target="_blank" rel="noreferrer">Meilisearch</a>。</li>
</ul>
<p>🚀行动建议20：磨刀不误砍柴工，写下一个文档之前，审视一下自己的工具箱，找到有效率问题的地方，比如 要在写作平台外手动处理的事情、可以用AI代替和增强的事情，列出一个效率改进计划逐步优化。</p>
<h3 id="模板库和参考示例" tabindex="-1">模板库和参考示例 <a class="header-anchor" href="#模板库和参考示例" aria-label="Permalink to &quot;模板库和参考示例&quot;">&ZeroWidthSpace;</a></h3>
<h4 id="vitepress-diataxis-template" tabindex="-1">VitePress Diátaxis Template <a class="header-anchor" href="#vitepress-diataxis-template" aria-label="Permalink to &quot;VitePress Diátaxis Template&quot;">&ZeroWidthSpace;</a></h4>
<p>我用Vitepress做了一个<a href="https://docs.code2life.top" target="_blank" rel="noreferrer">文档站点模板</a>，把很多最佳实践融入到模板里了，如果觉得还不错，Star/Fork<a href="https://github.com/Code2Life/vitepress-diataxis-template" target="_blank" rel="noreferrer">模板仓库</a>，创建自己的文档站点吧。</p>
<p><img src="https://filecdn.code2life.top/vite-press-template.png-ImageProcess" alt=""></p>
<h4 id="fern" tabindex="-1">Fern <a class="header-anchor" href="#fern" aria-label="Permalink to &quot;Fern&quot;">&ZeroWidthSpace;</a></h4>
<p>除了开源项目，也有一些专注文档领域的商业产品，我觉得目前把SDK+Docs生成做到极致的是<a href="https://buildwithfern.com/" target="_blank" rel="noreferrer">Fern</a>，不需要太多定制、不差钱可以试试Fern。</p>
<p><img src="https://filecdn.code2life.top/fern-example.png-ImageProcess" alt=""></p>
<h4 id="优秀文档站点案例" tabindex="-1">优秀文档站点案例 <a class="header-anchor" href="#优秀文档站点案例" aria-label="Permalink to &quot;优秀文档站点案例&quot;">&ZeroWidthSpace;</a></h4>
<p>下面是最近发现的几个优秀技术文档站，都恰到好处的分离了不同读者视角的关注点、文档结构清晰、遵循了技术写作的最佳实践，值得阅读借鉴。</p>
<ul>
<li><a href="https://developers.cloudflare.com/vectorize/" target="_blank" rel="noreferrer">Cloudflare Vectorize Docs</a></li>
<li><a href="https://docs.getport.io/quickstart/" target="_blank" rel="noreferrer">Port IDP Docs</a></li>
<li><a href="https://docs.restate.dev/" target="_blank" rel="noreferrer">Restate Docs</a></li>
</ul>
<p>🚀行动建议21：多读优秀的技术文档，学习他们的<strong>信息结构、内容风格</strong>。学到好的做法后，立刻在自己的文档上实践应用，再让AI给自己修改前后的内容打分评价，不断精进写作能力。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>好的技术文档都是相似的，能让读者 <strong>一眼找到、读得下去、立刻能用</strong>。达成这个目标不简单，需要掌握正确的方法，长期刻意练习。</p>
<p>通过这篇长文，我们系统性介绍了<strong>Diátaxis文档</strong>方法论、<strong>7条对写好技术文档至关重要的写作原则</strong>，最后简单介绍了高效写作的<strong>工具箱、示例和模板</strong>。</p>
<p>除了这些<strong>方法、原则、工具</strong>，还有<strong>21条行动建议</strong>穿插全文，拿来就能用。每一条建议背后，都有我亲身体会过的深刻教训。</p>
<blockquote>
<p>Build a living, breathing guide. -- Write the docs</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[2023年度个人总结]]></title>
            <link>https://code2life.top/blog/0077-2023-summary</link>
            <guid>https://code2life.top/blog/0077-2023-summary</guid>
            <pubDate>Sat, 17 Feb 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="_2023年度个人总结" tabindex="-1">2023年度个人总结 <a class="header-anchor" href="#_2023年度个人总结" aria-label="Permalink to &quot;2023年度个人总结&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="工作" tabindex="-1">工作 <a class="header-anchor" href="#工作" aria-label="Permalink to &quot;工作&quot;">&ZeroWidthSpace;</a></h2>
<p>工作方面，大部分时间花在沟通上，个人发展路线从<strong>偏技术转向了偏产品</strong>。主要聚焦在公司的基建改进、平台工程上，推动已经完成0-10构建过程的云平台，在全公司范围从10-100渐进落地，细节不便展开了。</p>
<p>去年工作中用了一些工具，回头看对效能提升挺有帮助的：</p>
<ol>
<li>Warp：AI加持的命令行工具</li>
<li>Arc：设计出色的新兴浏览器</li>
<li>OrbStack：无需虚拟机的本地Docker+Kubernetes</li>
<li>Sider：以及各类AI助手，Sider这个浏览器插件用的最多</li>
<li>Gamma：AI加持的Docs to Slides，用户体验极好</li>
</ol>
<h2 id="学习" tabindex="-1">学习 <a class="header-anchor" href="#学习" aria-label="Permalink to &quot;学习&quot;">&ZeroWidthSpace;</a></h2>
<p>去年的学习领域偏管理学、系统性思维。能力发展的重点放在分析、评估、创造这些<a href="https://zhuanlan.zhihu.com/p/165154137" target="_blank" rel="noreferrer">二阶思维</a>。</p>
<h3 id="管理学" tabindex="-1">管理学 <a class="header-anchor" href="#管理学" aria-label="Permalink to &quot;管理学&quot;">&ZeroWidthSpace;</a></h3>
<p>读了一些管理学相关的好书：</p>
<ul>
<li>《决断：成功的领导者怎样做出伟大的决断》-- Warren G. Bennis, Noel M. Tichy</li>
<li>《信任的速度》-- Stephen M.R. Covey</li>
<li>《相变》-- Safi Bahcall</li>
<li>《回头客战略》-- 谢家华</li>
<li>《卓有成效的管理者》-- Peter F. Drucker</li>
<li>《The Culture Map》-- Erin Meyer</li>
</ul>
<h3 id="技术" tabindex="-1">技术 <a class="header-anchor" href="#技术" aria-label="Permalink to &quot;技术&quot;">&ZeroWidthSpace;</a></h3>
<p>技术方面，看到张晋涛参与翻译的Alex Hidalgo新书《SLO与SLI:软件可靠性实践指南》出版了，买了纸质的精读了两遍，和平台工程的工作强相关，一些启发已经运用到产品设计中了。</p>
<p>前端方面，React稍微更熟了一些，代码写的不多。另外在新项目试用了Nuxt，开发体验确实很舒服，以后小东西可以都用Nuxt/Vitepress一把梭。</p>
<p>Rust入门学习资料二刷，不过实践场景比较少，目前仍然只能勉强看懂别人的代码，上项目还差远。</p>
<p>去年Star了180多个开源项目，回过头看，下面这些项目值得长期关注，对Web领域、SaaS行业开发者很有应用价值。</p>
<p><strong>Serverless方向</strong></p>
<ul>
<li><a href="https://github.com/supabase/edge-runtime" target="_blank" rel="noreferrer">Supabase Edge Runtime</a></li>
<li><a href="https://github.com/extism/extism" target="_blank" rel="noreferrer">Extism</a></li>
</ul>
<p><strong>AI方向</strong></p>
<ul>
<li><a href="https://github.com/Portkey-AI/gateway" target="_blank" rel="noreferrer">AI Gateway</a></li>
<li><a href="https://github.com/lmstudio-ai" target="_blank" rel="noreferrer">LM Studio</a></li>
<li>HuggingFace上每天冒出来的新的AI Models</li>
</ul>
<p><strong>前端方向</strong></p>
<ul>
<li><a href="https://zagjs.com/" target="_blank" rel="noreferrer">ZagJS Headless UI</a></li>
<li><a href="https://github.com/shadcn-ui/ui" target="_blank" rel="noreferrer">Shadcn UI</a></li>
</ul>
<p><strong>后端方向</strong></p>
<ul>
<li><a href="https://github.com/drizzle-team/drizzle-orm" target="_blank" rel="noreferrer">Drizzle ORM</a></li>
</ul>
<p><strong>大数据方向</strong></p>
<ul>
<li><a href="https://github.com/MaterializeInc/materialize" target="_blank" rel="noreferrer">Materialize</a></li>
<li><a href="https://github.com/datafuselabs/databend" target="_blank" rel="noreferrer">Databend</a></li>
</ul>
<p><strong>基建、平台类</strong></p>
<ul>
<li><a href="https://github.com/sanity-io/sanity" target="_blank" rel="noreferrer">Sanity Headless CMS</a></li>
<li><a href="https://github.com/PostHog/posthog" target="_blank" rel="noreferrer">PostHog</a></li>
<li><a href="https://github.com/backstage/backstage" target="_blank" rel="noreferrer">Backstage IDP</a></li>
<li><a href="https://github.com/SigNoz/signoz" target="_blank" rel="noreferrer">Signoz Observability Platform</a></li>
</ul>
<h3 id="其他" tabindex="-1">其他 <a class="header-anchor" href="#其他" aria-label="Permalink to &quot;其他&quot;">&ZeroWidthSpace;</a></h3>
<p>通识课方面，得到App的订阅课程，相比前几年少了很多，去年只订阅了《万维钢精英日课》。万sir讲AI的课程挺激动人心的，不过从业内视角看，对AI的落地有点过于乐观了。</p>
<p>剩下的就是更“无用”的书了，有本朋友推荐的《随风去野》挺有意思的，以漫画的形式讲了自行车环游中国的经历，其余的没什么推荐了。</p>
<h2 id="分享" tabindex="-1">分享 <a class="header-anchor" href="#分享" aria-label="Permalink to &quot;分享&quot;">&ZeroWidthSpace;</a></h2>
<p>我一直践行的理念是<strong>教学相长，输出就是最好的输入</strong>。</p>
<p>今年，在团队内部组织了两轮系列分享，其中我自己讲了两个管理学相关的话题。</p>
<p>一次是<a href="https://gamma.app/docs/Asana-p7wwmm97njay7h1" target="_blank" rel="noreferrer">用卖水果罐头的经历讲营销管理</a>，另一次是讲<a href="https://gamma.app/docs/Asana-p7wwmm97njay7h1" target="_blank" rel="noreferrer">时间管理</a>。</p>
<p>在公司范围，组织了一次参加AWS Summit回来后的分享。</p>
<p>我在AWS Summit主要听了安全合规方面的讲座，回来补个课在公司二次分享。其实在日常编程语言外，还有一些小众语言和框架，比如Dafny、Coq、Boogie，对实现<strong>可证明安全</strong>意义重大。那次在公司分享的比较浅，以后有时间可以写篇技术博客讲<strong>形式化验证</strong>。</p>
<p>除了这些正式分享活动之外，还有两次工作相关的。</p>
<p>一次是给我们平台用户的K8S入门培训，对比5年前，现在能用更通俗的语言给别人解释K8S、云原生，说明理解加深了。</p>
<p>另一次是<a href="https://gamma.app/docs/UX-Design-Basics-and-How-tos-gx2pic5qpdvg78c" target="_blank" rel="noreferrer">UX设计入门分享</a>，了解UX设计，对产品、前后端工程师都有意义。</p>
<p>除了演讲分享，博客沉淀了5篇：</p>
<ul>
<li><a href="/blog/0071-engineer-cross-culture.html">如何理解工程师的跨文化差异</a> -- 从个人经验出发，尝试总结中美印工程师的文化差异</li>
<li><a href="/blog/0072-k8s-in-30-min.html">30分钟了解Kubernetes</a> -- 从旧的K8S知识找到一些新的理解</li>
<li><a href="/blog/0073-4-types-of-r-n-d.html">软件项目怎么做好技术方案选型?</a> -- 悟了个浅显的<strong>研发类型矩阵</strong></li>
<li><a href="/blog/0074-tech-team.html">怎样构建卓越研发团队</a></li>
<li><a href="/blog/0075-enterprise-6-order-derivative.html">用6阶导数解构企业经营</a> -- 用数学思维理解企业经营本质</li>
</ul>
<h2 id="生活" tabindex="-1">生活 <a class="header-anchor" href="#生活" aria-label="Permalink to &quot;生活&quot;">&ZeroWidthSpace;</a></h2>
<p>生活上没有太大变化，享受平凡的幸福。孩子长大了，几个假期和家人去周边逛了逛，跑了6个地方：常州、长沙、杭州、苏州、青岛、霍山。</p>
<p>还有一些珍贵的回忆，就在附近。一次是和娃在幼儿园表演绘本故事《猜猜我有多爱你》，另一次是幼儿园组织去农场，跟老婆孩子体验了挖芋头的快乐。</p>
<p><img src="https://filecdn.code2life.top/2024_08_27_2b1d8e624edfc30b97355262866fdb87af6febde2f3a34162b622869dda94d71.png" alt=""></p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>去年是自我革新关键的一年，多数时间挺焦虑的，还好有家人、朋友、同事的的支持，留下了许多美好回忆，也沉淀了不少知识。</p>
<p>总结23年的关键词：<strong>焦虑，转型，自我认知</strong>。</p>
<p>展望24年的关键词，希望是：<strong>行动，行动，行动</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[用6阶导数解构企业经营]]></title>
            <link>https://code2life.top/blog/0075-enterprise-6-order-derivative</link>
            <guid>https://code2life.top/blog/0075-enterprise-6-order-derivative</guid>
            <pubDate>Thu, 15 Feb 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="用6阶导数解构企业经营" tabindex="-1">用6阶导数解构企业经营 <a class="header-anchor" href="#用6阶导数解构企业经营" aria-label="Permalink to &quot;用6阶导数解构企业经营&quot;">&ZeroWidthSpace;</a></h1>
<nav class="table-of-contents"><ul><li><a href="#企业是什么">企业是什么</a><ul><li><a href="#企业是一棵树">企业是一棵树</a></li><li><a href="#企业的-导数">企业的“导数”</a></li></ul></li><li><a href="#为什么有财务结果">为什么有财务结果？</a><ul><li><a href="#一阶导-业务">一阶导：业务</a></li><li><a href="#二阶导-组织结构、流程、系统">二阶导：组织结构、流程、系统</a></li><li><a href="#三阶导-战略">三阶导：战略</a></li><li><a href="#四阶导-使命、愿景">四阶导：使命、愿景</a></li><li><a href="#五阶导-企业文化、价值观">五阶导：企业文化、价值观</a></li><li><a href="#六阶导-人">六阶导：人</a></li></ul></li><li><a href="#六阶导数模型对企业经营的启发">六阶导数模型对企业经营的启发</a><ul><li><a href="#_1-导数是强因果">1. 导数是强因果</a></li><li><a href="#_2-每一阶导数应该以什么频率变革">2. 每一阶导数应该以什么频率变革</a></li><li><a href="#_3-高阶导数的滞后效应">3. 高阶导数的滞后效应</a></li><li><a href="#_4-沟通是不同阶导数的传导介质">4. 沟通是不同阶导数的传导介质</a></li></ul></li><li><a href="#总结">总结</a></li></ul></nav>
<h2 id="企业是什么" tabindex="-1">企业是什么 <a class="header-anchor" href="#企业是什么" aria-label="Permalink to &quot;企业是什么&quot;">&ZeroWidthSpace;</a></h2>
<p>去年听一个咨询机构的公开课，讲到组成企业的“四梁八柱”，<strong>屋顶是使命、战略、价值观，柱子是产品、营销、运营、投融资，地基是管理、人才、趋势</strong>。</p>
<p>用房子比喻企业经营非常形象，那能不能用稍微严谨些的<strong>数学模型</strong>来表达呢？</p>
<p>这篇文章分享从理论角度对企业经营的思考。</p>
<h3 id="企业是一棵树" tabindex="-1">企业是一棵树 <a class="header-anchor" href="#企业是一棵树" aria-label="Permalink to &quot;企业是一棵树&quot;">&ZeroWidthSpace;</a></h3>
<p>我们把企业想象成一棵长果子的树，促成一颗树结果子的，有阳光、雨露这些外部因素，有树本身的结构，<strong>树叶、树枝、树杈、树干、土壤、树根</strong>。</p>
<p>那促成企业之树产生“利润果实”的是什么呢？我思考了很久，发现组成企业的元素和一棵树的元素，是这样对应的：</p>
<ul>
<li>财务利润 -- 果实</li>
<li>业务 -- 树叶</li>
<li>组织、流程、系统 -- 树枝</li>
<li>战略 -- 树杈</li>
<li>使命、愿景 -- 树干</li>
<li>企业文化、价值观 -- 土壤</li>
<li>人 -- 树根</li>
</ul>
<p><img src="https://filecdn.code2life.top/7-level-company.png-ImageProcess" alt=""></p>
<p>为什么要这样对应呢？乍一看不太合理：</p>
<ul>
<li>战略、使命、愿景这些“很虚”的东西竟然是树干？</li>
<li>企业文化、价值观这些“更虚”的东西竟然是土壤？</li>
<li>企业人员流动频繁，人竟然是树根？</li>
</ul>
<p>带着这些疑问，我们一步步抽丝剥茧，解构企业经营的本质。</p>
<h3 id="企业的-导数" tabindex="-1">企业的“导数” <a class="header-anchor" href="#企业的-导数" aria-label="Permalink to &quot;企业的“导数”&quot;">&ZeroWidthSpace;</a></h3>
<p>“导数”是微积分的基本概念，其几何意义是函数的切线斜率，现实意义，即函数变化的原因。</p>
<p>比如驾驶一辆车，加速度“导致”了速度，加速度是速度的一阶导。踩油门的力度又“导致”了加速度的变化，踩油门的力度就是加速度的一阶导，速度的二阶导，行驶距离的三阶导。</p>
<p>再举个例子，在经济学、企业管理领域，边际(Margin)这个词用的很多，边际成本、边际利润、边际贡献... &quot;边际&quot;其实就是导数换一种说法，边际成本可以理解为“<strong>每多生产一件产品，要多付出的单位成本</strong>”。因此，<strong>边际成本，就是总成本变化的原因，也就是成本的导数</strong>。</p>
<p>导数思维，就是一层一层追问Why，理解事物之间的因果关系。</p>
<p>因此，我们从<strong>财务结果</strong>出发，求导问Why，一直溯源下去，就能找到企业经营的本质。</p>
<h2 id="为什么有财务结果" tabindex="-1">为什么有财务结果？ <a class="header-anchor" href="#为什么有财务结果" aria-label="Permalink to &quot;为什么有财务结果？&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="一阶导-业务" tabindex="-1">一阶导：业务 <a class="header-anchor" href="#一阶导-业务" aria-label="Permalink to &quot;一阶导：业务&quot;">&ZeroWidthSpace;</a></h3>
<p>第一阶导数显而易见，有<strong>财务结果</strong>是因为有<strong>客户购买</strong>企业的<strong>产品和服务</strong>。</p>
<p>从企业内部看，能赚钱是因为企业有<strong>业务</strong>，这些业务<strong>产生了客户能购买的产品和服务</strong>。</p>
<p>企业经营的一阶导，是业务。</p>
<h3 id="二阶导-组织结构、流程、系统" tabindex="-1">二阶导：组织结构、流程、系统 <a class="header-anchor" href="#二阶导-组织结构、流程、系统" aria-label="Permalink to &quot;二阶导：组织结构、流程、系统&quot;">&ZeroWidthSpace;</a></h3>
<p>再问一层，<strong>客户为啥要买你的业务呢</strong>？</p>
<p>买东西只有一个理由：<strong>值得买</strong>。</p>
<p>客户有很多问题要解决，这些问题就是<strong>痛点、痒点</strong>。企业看到了，就把<strong>解决这些问题的方案，包装成产品和服务</strong>。</p>
<p>而业务，就是生产、销售这些产品和服务的过程。</p>
<p>业务能在<strong>多大程度上</strong>解决掉客户的问题，带给客户的<strong>感知价值</strong>有多少，就导致了多少<strong>购买行为</strong>。</p>
<p>从企业内部看，业务是什么呢？</p>
<p><strong>生产、销售的业务流水线</strong>像一台机器，那么这台机器是怎么构成的呢？</p>
<p>凑近了看，组成企业，让企业能够产生业务的，是<strong>组织结构、流程、系统</strong>。</p>
<p>越大的企业，要解决客户的问题越多，就要求这台机器越复杂，<strong>管理学的大多数内容</strong>都在这一层。</p>
<p>比如，怎么管理组织内的信息沟通、知识传播？用哪些方法激励员工？组织架构是职能式、矩阵式、还是阿米巴式？怎么让生产线更加多快好省？销售的每个环节怎么提升转化率？</p>
<p>每一个<strong>组织结构、流程、系统</strong>，都是在<strong>定义、调优</strong>企业这台机器的零部件和零部件的装配关系，让“价值”生产出来。</p>
<p>客户往这台机器输入一个问题，就输出一个答案。从输入到输出<strong>价值增加</strong>的过程，形成了一条<strong>价值流</strong>，也是一条<strong>业务流</strong>。</p>
<p>因此，企业经营的二阶导，是这台“机器”的组成，也就是<strong>组织结构、流程、系统</strong>。</p>
<h3 id="三阶导-战略" tabindex="-1">三阶导：战略 <a class="header-anchor" href="#三阶导-战略" aria-label="Permalink to &quot;三阶导：战略&quot;">&ZeroWidthSpace;</a></h3>
<p><strong>为什么会不同企业的组织结构、流程、系统各不相同呢</strong>？</p>
<p>因为他们选择的<strong>问题域</strong>不同。选择了<strong>解决这一类问题，而不是那一类问题</strong>，这种选择，导致了企业必然要演化出<strong>与问题相匹配</strong>的组织结构、流程、系统。</p>
<p>那么问题也就转换成，“为什么企业要做不同的问题域选择呢”？</p>
<p>因为企业的经营者，<strong>看到了</strong>或者<strong>预测出</strong>市场会出现一些<strong>普遍的需求</strong>，企业会结合内部拥有的<strong>资源</strong>和<strong>能力</strong>，评估自己的<strong>优势</strong>和<strong>劣势</strong>，再分析外部的<strong>机会</strong>和<strong>威胁</strong>，权衡<strong>风险</strong>和<strong>收益</strong>，做出了<strong>战略选择</strong>。</p>
<p>一旦做出了战略选择，执行战略的过程，必然要动组织结构，进而影响到<strong>系统、流程、工具</strong>，让企业这台机器，生产出不一样的业务。</p>
<p>就像一棵树移栽后，太阳的位置变了，这颗树必然会抑制背阴的一面，把营养供给向阳的一面，长出新的树杈、树枝、树叶，结出新的果实。</p>
<p>因此，企业经营的三阶导，是<strong>战略</strong>。</p>
<h3 id="四阶导-使命、愿景" tabindex="-1">四阶导：使命、愿景 <a class="header-anchor" href="#四阶导-使命、愿景" aria-label="Permalink to &quot;四阶导：使命、愿景&quot;">&ZeroWidthSpace;</a></h3>
<p>继续深挖，为什么有这样的<strong>战略</strong>呢？</p>
<p>是选择突出差异化，还是成本领先？是聚焦，还是多元化？</p>
<p>战略决策，是咨询机构和经营者开会讨论出来的么？是由SWOT分析、五力模型这些理论决定的么？</p>
<p>这些都是工具和方法论，而不是深层原因。</p>
<p>深层原因仍要看市场，看企业选择了哪个<strong>行业</strong>。</p>
<p>一旦行业领域确定了，这个行业的<strong>发展阶段和趋势</strong>就能分析出来。可以推导这个行业，出有哪些<strong>使命有待完成</strong>。</p>
<p>有了行业发展阶段和趋势，战略选择时要关注的<strong>行业关键挑战</strong>、细分市场上<strong>客户有哪些、供应商有哪些、替代品、当前和潜在的竞品</strong>，这些答案自然浮出水面了。</p>
<p>也就是说，选择或放弃某个或某类行业时，就是选择或放弃了这个领域对应的<strong>使命和愿景</strong>，也注定了<strong>长期有哪些可能的战略有机会选择</strong>。</p>
<p>因此，企业经营的四阶导，是<strong>使命和愿景</strong>。</p>
<h3 id="五阶导-企业文化、价值观" tabindex="-1">五阶导：企业文化、价值观 <a class="header-anchor" href="#五阶导-企业文化、价值观" aria-label="Permalink to &quot;五阶导：企业文化、价值观&quot;">&ZeroWidthSpace;</a></h3>
<p>继续问，为什么有这样的使命和愿景呢？</p>
<p>我的思考是<strong>企业文化和价值观</strong>。</p>
<p>一些经营者会说，“我们在战略转型期，升级了使命和愿景，因此，要发展对应的企业文化，优化价值观”。这犯了一个因果倒置的错误，实际上，如果没有文化、价值观、意识层面的引导和宣贯，做任何重大变革都会阻力重重。</p>
<p>企业文化不是口号，而是一种<strong>既定事实</strong>。同样，价值观也不是标语，而是<strong>判断决策质量的最后一把尺子</strong>。</p>
<p>组成企业的<strong>人</strong>携带的文化和价值观的总和，就是<strong>企业的文化、价值观</strong>。这些文化和价值观的源头来自创始团队，像公司的<strong>基因</strong>。</p>
<blockquote>
<p>No companies have a culture, but every company is a Culture -- Peter Thiel</p>
</blockquote>
<p>带着这层理解，不难得出：是<strong>当前的</strong>企业文化、价值观，<strong>度量、约束，甚至决定着</strong>企业使命和愿景的定义。</p>
<p>举个例子，微软CEO纳德拉上任时第一件事，不是改写愿景、使命，和高管们商量战略，而是给所有经理层布置作业读《非暴力沟通》、向全体员工致信鼓励创新。投入巨大的精力，做企业文化变革。</p>
<p>文化和价值观层面的变革是第一件事，然后再传递到Cloud-first这类战略层面的变革。</p>
<p>因此，企业经营的五阶导，是<strong>企业文化和价值观</strong>。</p>
<h3 id="六阶导-人" tabindex="-1">六阶导：人 <a class="header-anchor" href="#六阶导-人" aria-label="Permalink to &quot;六阶导：人&quot;">&ZeroWidthSpace;</a></h3>
<p>为什么有这样的企业文化和价值观呢？在上一节的分析已经给出答案了。</p>
<p>人，带着自己的价值观加入企业，融入、生长，成为企业文化和价值观的一部分。</p>
<p>企业经营的六阶导，也是企业之根本，是<strong>人</strong>。</p>
<h2 id="六阶导数模型对企业经营的启发" tabindex="-1">六阶导数模型对企业经营的启发 <a class="header-anchor" href="#六阶导数模型对企业经营的启发" aria-label="Permalink to &quot;六阶导数模型对企业经营的启发&quot;">&ZeroWidthSpace;</a></h2>
<p>推导出这个模型，对企业实际经营有什么作用呢？我们从4个方面展开说明。</p>
<h3 id="_1-导数是强因果" tabindex="-1">1. 导数是强因果 <a class="header-anchor" href="#_1-导数是强因果" aria-label="Permalink to &quot;1. 导数是强因果&quot;">&ZeroWidthSpace;</a></h3>
<p>上下几层之间应该<strong>有序联动</strong>。上一层要在下一层<strong>之前</strong>动，反向动或者下一层不跟着动，必出问题。</p>
<p>比如，企业要做战略变革，正常路径是战略先动、组织架构再动、最终推动业务层动。</p>
<p>如果没有对应的组织架构重组，就像树上长一个新树杈，无枝、无叶，必然凋零。</p>
<p>如果战略调整范围更大，触及到使命和愿景层面的修正了，这时千万不能着急执行，而是要让企业高层重新审视“<strong>公司是什么，想要成为什么</strong>”这个问题了。</p>
<p>为什么必须联动呢？反向思考，一旦新的战略地图无法呼应<strong>公司的使命和愿景</strong>，那很可能会造成基层员工的迷茫、高层的决策冲突。</p>
<p>强因果的另一个启发是，对于高层管理者，要把力气花在四两拨千斤的高阶导数上，事半功倍。CEO要是盯着业务的螺丝钉搞“微管理”，会导致低阶导数的失衡。</p>
<h3 id="_2-每一阶导数应该以什么频率变革" tabindex="-1">2. 每一阶导数应该以什么频率变革 <a class="header-anchor" href="#_2-每一阶导数应该以什么频率变革" aria-label="Permalink to &quot;2. 每一阶导数应该以什么频率变革&quot;">&ZeroWidthSpace;</a></h3>
<p>在Vuka时代，社会和市场在极快发展，要求企业从微观到宏观层面适应变化。</p>
<p>持续变革，是企业生长的节奏和脉搏，有脉搏才能生存下去。</p>
<p>导数阶数越低，受影响的因素越多，变动频率应该越高；反之，越高阶的层级，影响面越广，变动频率应该越低。</p>
<p>举个例子，从我工作过的公司看，改变<strong>业务、组织、流程</strong>是按月计的，改变<strong>战略</strong>是3到5年计的，改变<strong>使命、愿景、文化、价值观、核心员工</strong>是5到10年计的。</p>
<p>越低层级的导数，代表越具体、越细节的知识的变化，更新越快说明企业的沉淀和成长更快，越能适应快速变化的市场环境；</p>
<p>越高层级的导数，代表着越大范围的共识，而达成新的共识的成本越高。</p>
<h3 id="_3-高阶导数的滞后效应" tabindex="-1">3. 高阶导数的滞后效应 <a class="header-anchor" href="#_3-高阶导数的滞后效应" aria-label="Permalink to &quot;3. 高阶导数的滞后效应&quot;">&ZeroWidthSpace;</a></h3>
<p>现实世界有一个永恒的时间变量，当高阶导数变化时，变化需要时间、资源把信息传递到下一层。</p>
<p>因此，所有层级之间的级联变化，都是有<strong>滞后效应</strong>的。</p>
<p>比如，今天为了解决生产质量问题，调整的组织结构和质量控制的流程，可能下个月才会体现到产品上，半年后才会对财务结果有正向影响。</p>
<p>而到企业文化、组织战略这种层面的变革，甚至要两三年才能传导到财务结果。</p>
<p>经营者理解了这个推论，就不会容易急功近利，有的项目就是<strong>长期的，难以量化的</strong>，但必不可少。</p>
<p>举个例子，对于稍微大一些的企业，会思考企业文化建设、员工培训和发展计划这些高阶导数的问题。很多急功近利的做法是找个毕业生立刻就做，做了一个月领导就要结果。</p>
<p>实际上这类工作，不仅要非常有经验的、能够理解和践行长期主义的专业人士才，还要足够的时间，复利效应才能体现出来。</p>
<h3 id="_4-沟通是不同阶导数的传导介质" tabindex="-1">4. 沟通是不同阶导数的传导介质 <a class="header-anchor" href="#_4-沟通是不同阶导数的传导介质" aria-label="Permalink to &quot;4. 沟通是不同阶导数的传导介质&quot;">&ZeroWidthSpace;</a></h3>
<p>上一个推论是导数之间的滞后效应，那么，有没有办法减轻这种滞后，让企业经营和变革的速度更快呢？</p>
<p>为什么一些中小型企业变革那么快，一些大型企业积重难返？</p>
<p>这里的关键，就是不同阶导数之间的<strong>传导介质</strong>，即<strong>沟通</strong>。</p>
<p>有的小公司员工身兼数职，沟通成本几乎能忽略不计；有的大公司一个讨论要几周时间才能落地，差距就在<strong>沟通效率</strong>上。</p>
<p><strong>大公司病</strong>，表象是“部门墙”、“躺平”、“推诿”，这些是表象，关键根因之一，是员工超过150邓巴数后，落下的<strong>沟通病</strong>。</p>
<p>怎么解决呢？我们先看每一阶导数，背后的<strong>知识</strong>或<strong>共识</strong>是什么。</p>
<ul>
<li>文化和价值观：<strong>价值共识</strong></li>
<li>使命和愿景：<strong>远景共识</strong></li>
<li>战略：<strong>目标共识</strong></li>
<li>组织、流程、系统：<strong>组织知识</strong></li>
<li>业务：<strong>领域知识</strong></li>
</ul>
<p>前三个是<strong>共识</strong>，要让公司所有人都理解和认同的。</p>
<p>提升这几层的传导速度，必须建立<strong>全员广播+跨层级反馈</strong>的沟通机制。比如定期组织 All hands, town hall会议，透明开放的与全员沟通；定期的企业文化、战略解读；建立有效的直通高层的反馈渠道等等。</p>
<p>后两个是<strong>知识</strong>，要让相关业务的人快速获取和演进。</p>
<p>提升这几层传导速度，必须建立<strong>共享+共创</strong>的沟通机制。比如维护公开及时准确的组织架构信息；鼓励业务知识库的积累和维护；用好实时协作文档系统、IM系统、在线会议系统，让公司任意一组人能快速发现问题、做决策、解决问题。</p>
<p>总之，大公司要做到“大象跳舞”，必须要有一套完善的沟通架构，<strong>引导共识、传递知识、分享信息、获取反馈</strong>，让不同阶导数之间的<strong>传导效率产生质变</strong>。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本篇记录了我对企业经营的抽象思考，企业与树类一样，<strong>远看是果、叶，近看是枝、干，深看是根、土</strong>。</p>
<p>首先，给出了企业经营中的关键概念、概念之间的导数关系，</p>
<p>然后，通过逻辑分析，一步步推导出六阶导数模型，</p>
<p>最后，我们从每阶导数的因果联动、变革频率、滞后性、信息属性4个方面，展开分析了导数模型对企业经营有哪些现实意义。</p>
<blockquote>
<p>不抽象，就无法深入思考，不还原，就看不到本来面目。 -- 刘润</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[怎样构建卓越研发团队？]]></title>
            <link>https://code2life.top/blog/0074-tech-team</link>
            <guid>https://code2life.top/blog/0074-tech-team</guid>
            <pubDate>Sun, 14 Jan 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="怎样构建卓越研发团队" tabindex="-1">怎样构建卓越研发团队？ <a class="header-anchor" href="#怎样构建卓越研发团队" aria-label="Permalink to &quot;怎样构建卓越研发团队？&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h2>
<nav class="table-of-contents"><ul><li><a href="#目录">目录</a></li><li><a href="#卓越研发的标尺">卓越研发的标尺</a></li><li><a href="#《决断》-明确战略目标是提升产品力的前提">《决断》-- 明确战略目标是提升产品力的前提</a><ul><li><a href="#韦尔奇与伊梅尔特的战略决断">韦尔奇与伊梅尔特的战略决断</a></li><li><a href="#战略与产品力的关系">战略与产品力的关系</a></li><li><a href="#怎么在实践中提升产品力">怎么在实践中提升产品力</a></li></ul></li><li><a href="#《信任的速度》-有信任才有执行力">《信任的速度》-- 有信任才有执行力</a><ul><li><a href="#对信任的解构">对信任的解构</a></li><li><a href="#一个跨四时区临时团队的故事">一个跨四时区临时团队的故事</a></li><li><a href="#塔克曼团队发展模型">塔克曼团队发展模型</a></li></ul></li><li><a href="#《相变》-创造力不能被计划">《相变》-- 创造力不能被计划</a><ul><li><a href="#从天文仪到蒸汽机">从天文仪到蒸汽机</a></li><li><a href="#创造力的摇篮-布什-韦尔平衡">创造力的摇篮：布什-韦尔平衡</a></li><li><a href="#无心插柳柳成荫">无心插柳柳成荫</a></li></ul></li><li><a href="#总结">总结</a></li></ul></nav>
<h2 id="卓越研发的标尺" tabindex="-1">卓越研发的标尺 <a class="header-anchor" href="#卓越研发的标尺" aria-label="Permalink to &quot;卓越研发的标尺&quot;">&ZeroWidthSpace;</a></h2>
<p>两年前开始做平台工程，一方面大量基建问题堆积如山，必须快速迭代；另一方面，平台工程是飞行中换引擎，质量、安全性都有很高要求。项目开始时，而团队经验并不丰富、资源有限，很多地方做的不到位，因此，只有不断提升整体水平，成长为一个卓越的研发团队，才能真正解决问题。</p>
<p>那么，哪些是<strong>卓越研发团队的关键特质</strong>？我一直在思考这个问题。</p>
<p>在去年读的书中，有三本书触动很深：</p>
<ul>
<li>《决断：成功的领导者怎样做出伟大的决断》-- Warren G. Bennis, Noel M. Tichy</li>
<li>《信任的速度》-- Stephen M.R. Covey</li>
<li>《相变》-- Safi Bahcall</li>
</ul>
<p>在这三本书中，我找到了卓越研发团队的三把标尺 —— <strong>产品力、执行力、创造力</strong>。</p>
<p><strong>为什么是这三个特质</strong>？<strong>在人和组织层面，如何培育这些特质呢</strong>？</p>
<p>下面，我们从<strong>每本书的核心内容出发，结合真实案例</strong>逐个分析解答。</p>
<h2 id="《决断》-明确战略目标是提升产品力的前提" tabindex="-1">《决断》-- 明确战略目标是提升产品力的前提 <a class="header-anchor" href="#《决断》-明确战略目标是提升产品力的前提" aria-label="Permalink to &quot;《决断》-- 明确战略目标是提升产品力的前提&quot;">&ZeroWidthSpace;</a></h2>
<p>《决断》的作者沃伦•本尼斯、诺埃尔•蒂奇，是两位领导力理论的先驱。全书首先明确了决断的定义和框架，再讲述了世界顶级CEO的决断案例，包括通用电气、宝洁、百胜、波音这些家喻户晓的公司，最后点出“传统决策”和“决断”的差异，上升到理论高度进行总结。</p>
<p>这本书副标题原文是：“How winning leaders make great calls”，但并不是只有领导者才能看，任何人去读，都能在这些CEO生动的故事中得到启发，提升格局和领导力。</p>
<p>书中有成功的也有失败的案例，总体分成三类：人的决断、战略决断、危机决断，前后两种决断更多是领导力范畴，这里我们重点看中间的：<strong>战略决断</strong>。</p>
<p><img src="https://filecdn.code2life.top/2024_08_26_62a72fb9464a57178715e20a7b2c7761e4a9a16ea387db0c3b5d1b4caf48ccb5.png" alt=""></p>
<h3 id="韦尔奇与伊梅尔特的战略决断" tabindex="-1">韦尔奇与伊梅尔特的战略决断 <a class="header-anchor" href="#韦尔奇与伊梅尔特的战略决断" aria-label="Permalink to &quot;韦尔奇与伊梅尔特的战略决断&quot;">&ZeroWidthSpace;</a></h3>
<p>韦尔奇在1981年成为通用电气CEO之后，解雇了200多名战略规划师，他说：&quot;<strong>领导者需要拥有自己的战略，而不是官僚作风的参谋</strong>&quot;。通过清晰的战略决断、对组织的精简、发挥人才潜力，韦尔奇在任期内，将通用电气的9个事业部做到了世界500强、市场价值增长30多倍。</p>
<p>他的接班人杰夫·伊梅尔特，也继续战略决断的故事主线，通过对市场需求和企业能力的精确理解，不断寻找最佳方向，不断重组、转型，续写了通用电气的传奇，连续保持了10%的高增长。</p>
<p>时至今日，这家百年企业虽然不再有千禧年前的风光，但从近年来重组为航天、医疗、能源三个公司、卖掉家电这些资产的动向，仍然可以看到清晰的战略方向。</p>
<h3 id="战略与产品力的关系" tabindex="-1">战略与产品力的关系 <a class="header-anchor" href="#战略与产品力的关系" aria-label="Permalink to &quot;战略与产品力的关系&quot;">&ZeroWidthSpace;</a></h3>
<p>那么，回到软件研发领域，为什么<strong>战略决断对研发团队的每个人都很重要呢</strong>？</p>
<p>软件研发是知识密集型劳动，不确定性高，产出难以量化。越复杂的软件项目，模块级别的产品负责人、工程师掌握越多的业务信息。信息是一切决策的前提，因此，研发团队的<strong>权力结构通常是自下而上的</strong>，模块级别的负责人<strong>既影响战略方向制定，也是战略落地的关键执行单元</strong>。</p>
<p>一旦底层决策与整个产品的战略目标偏移，必然导致大量资源浪费、形成不了合力，这也是不同研发团队效能相差十倍甚至百倍的关键原因。<strong>方向错了，其他一切都毫无意义</strong>。</p>
<p>所以，研发团队的每个人，特别是产品负责人，都要学习杰克·韦尔奇、杰夫·伊梅尔特那样的战略意识，要有对<strong>外部趋势、市场的准确把握，还要有对自身优势、劣势的精确理解</strong>。战略的理解和决断能力具备了，再去<strong>躬身前往细节世界，理解一个个具体用户的痛点、提炼需求，用软件模型去表达真实场景的普遍问题</strong>，最后<strong>回到宏观战略目标做减法、找增量，汇集力量把一个个阶段性目标打穿</strong>。</p>
<blockquote>
<p>产品力 = (对战略目标的理解 x 对用户的理解) x 建模表达能力</p>
</blockquote>
<h3 id="怎么在实践中提升产品力" tabindex="-1">怎么在实践中提升产品力 <a class="header-anchor" href="#怎么在实践中提升产品力" aria-label="Permalink to &quot;怎么在实践中提升产品力&quot;">&ZeroWidthSpace;</a></h3>
<p>在项目中，我们也一直在实践和改进，和组员做1:1沟通时，我最喜欢探讨的一个问题就是：“如果你是整个产品的负责人/公司的CEO，你会做哪几年事情，未来几年的你觉得方向应该是什么？”，每次讨论都有不同的收获，经过多轮自下而上、自上而下的沟通和决策，我们所负责的平台产品，其战略方向的故事主线越来越清晰，用《决断》中的定义就是一个包括了想法、价值观、锐气的“Teachable point of view” (TPOV)。</p>
<p>另外，在每季度确定下阶段排哪些任务之前，我会组织团队头脑风暴，对需求列表逐个问两个问题：</p>
<ol>
<li>这件事与我们大目标的匹配度可以打几分？</li>
<li>换到用户角度思考，这件事对用户的重要性有几分？</li>
</ol>
<p>当大家给出量化的评分时，自然会从各种角度想到“这件事重要或不重要”的关键原因，让每个需求有了是否要做减法的依据，也完善了每个需求对应的用户故事。这个过程会花很多时间讨论、思考，搞清楚<strong>每件事在战略地图中的位置、每个人在价值链中的意义</strong>，看起来慢，实则通过产品力的磨炼，来减少大量返工和无效工作，是非常划算的。</p>
<p><strong>越大的项目，越要谋定而后动</strong>，“<strong>Think Slow, Act Fast</strong>”。</p>
<p>几个季度执行下来，能明显感到大家的产品力提高了。很多同事不再会只从自己的技术思维出发，简单的说“我想做一个XXX”，而是能自主做出“哪些需求不该做”、“哪些核心功能中用户仍然有痛点需要改进”的决断，真正把<strong>战略目标和用户痛点一起刻到了岩石上</strong>。</p>
<h2 id="《信任的速度》-有信任才有执行力" tabindex="-1">《信任的速度》-- 有信任才有执行力 <a class="header-anchor" href="#《信任的速度》-有信任才有执行力" aria-label="Permalink to &quot;《信任的速度》-- 有信任才有执行力&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="对信任的解构" tabindex="-1">对信任的解构 <a class="header-anchor" href="#对信任的解构" aria-label="Permalink to &quot;对信任的解构&quot;">&ZeroWidthSpace;</a></h3>
<p>《信任的速度》是我司CEO Eric S. Yuan在全员大会上，推荐所有人看的三本书之一。作者史蒂芬·科维还写过另一本传播更广泛的《高效能人士的七个习惯》，读这类畅销书中的经典，要有一些阅历才能深刻体会，否则很容易认为只是心灵鸡汤。《信任的速度》把“信任”这个看似简单的概念解构的很彻底，首先定义了构建信任的积木：信用（Credit）。信用具体指什么呢？有四个核心要素：</p>
<ul>
<li>诚实：基本的道德要求</li>
<li>动机：关心共同利益而非私利</li>
<li>能力：具备兑现承诺的天赋、态度、技能、知识</li>
<li>成果：已经实现过的承诺</li>
</ul>
<p>与广义上的“信一个人的人品”不同，信用的要素包括<strong>能力与成果</strong>，就像一个人品再好的老师也没办法去给病人做手术、一个刚还没毕业的医生也没办法让病人信任其医术。</p>
<p>其次，作者从影响圈范围的角度，把信任分为五个层次，讲解了构建每一层信任的方法论和必要的行为：</p>
<ul>
<li>自我的信任</li>
<li>关系的信任</li>
<li>组织的信任</li>
<li>市场的信任</li>
<li>社会的信任</li>
</ul>
<p><strong>每一层信任的递进，都会带来指数级的效率提升，形成改变世界的力量。反之，没有信任这块人类文明的基石，一切沟通协作都无从谈起</strong>。</p>
<p><img src="https://filecdn.code2life.top/2024_08_26_9a95606b089780c7b873c1deefc77698be8acb73b570be6d71b6293d464abdc7.png" alt=""></p>
<h3 id="一个跨四时区临时团队的故事" tabindex="-1">一个跨四时区临时团队的故事 <a class="header-anchor" href="#一个跨四时区临时团队的故事" aria-label="Permalink to &quot;一个跨四时区临时团队的故事&quot;">&ZeroWidthSpace;</a></h3>
<p>2021年底，在我的直属经理支持下，打算开始做公司级别的平台工程，那时我的本职是微服务开发框架的架构工作，自己团队内只有一位研发能抽出时间，无法完成这么大的工程。本着“人多力量大”的朴素想法，我去找了所有利益相关方，正好有几个团队也非常想解决缺乏基建平台、十几条业务线各自为战、职能部门孤岛这些<strong>阻碍业务发展的系统性问题</strong>，我们临时组建了一个跨中美印的四个时区和文化的虚拟团队。</p>
<p>很快，由于对目标的理解、工作方式不同，前两个月就在设计和技术选型就上产生了不少冲突，项目推进缓慢。</p>
<p>好在上层出面及时帮助做了目标纠偏，方向上达成了一致。具体到每个模块，我和另外一位项目发起人，靠着一次一次组织同时区的、跨时区的会议、不断讨论直到达成共识的“蛮力”，推进产品研发。</p>
<p>半年后平台产品发布，我把之前在做微服务框架时积累的内部客户，转换成了平台的首批用户，一个“反馈-改进”的<strong>增强回路</strong>搭建出来了。</p>
<p>磨合一年后，每个模块的关键贡献者不言自明，随着信任关系的不断强化，职责和分工越来越明确。一周一个迭代的快速改进，让用户对我们的信任也逐渐增强，同时，我们也尽可能让项目的决策过程和进度对所有人透明公开。</p>
<p>没有哪个团队刚组建就能互相高度信任，经过了太多曲折，现在回顾这个故事，很多地方本可以做的更好。</p>
<p>两年后，公司大部分业务线、超过400个服务组件，已经迁移到了我们构建的标准化运维平台、计算平台。这些结果传递到高层，形成对平台工程大方向的信任，又间接促成了多个部门的组织架构调整。</p>
<p><strong>这就是信任飞轮带来的执行力</strong>。</p>
<h3 id="塔克曼团队发展模型" tabindex="-1">塔克曼团队发展模型 <a class="header-anchor" href="#塔克曼团队发展模型" aria-label="Permalink to &quot;塔克曼团队发展模型&quot;">&ZeroWidthSpace;</a></h3>
<p>前不久学到了塔克曼团队发展阶段模型，发现上面这段经历完美符合了理论模型。塔克曼论述了团队发展必然经过 <strong>Forming, Storming, Norming, Performing</strong>这4个阶段。塔克曼这篇1965年的16页论文，是<strong>组织发展理论</strong>的开山之作。模型论述了团队的整体效能是一条微笑曲线，<strong>在Storming结束前，效能逐渐下降，而只要渡过了Storming阶段，效能会稳步上升，最终达到高效能状态</strong>。</p>
<p>从信任的角度看，<strong>团队的效能和执行力，与内部信任程度正相关</strong>。</p>
<ul>
<li>刚开始团队被共同目标组织起来的时候，团队成员互相不熟悉，有一个薄弱的信任基础和沟通距离，因此在<strong>Forming阶段</strong>通常呈现出“假和谐”；</li>
<li>随着各种分歧的出现和积累，信任逐渐降低到冰点，士气低落，就标志着<strong>Storming阶段</strong>来了，多数失败的、名存实亡的项目和创业公司，就是倒在了Storming阶段；</li>
<li>如果团队渡过了这个难关，随着信任曲线的上升、经验的积累，团队开始自主调整资源分配、制定流程和规范，进入了<strong>Norming阶段</strong>；</li>
<li>正向成果的不断积累、失败经验的沟通和学习，让信任曲线上升到最高点，团队执行力也达到顶峰，进入长期的<strong>Performing阶段</strong>。</li>
</ul>
<p>那么，作为团队领导者，<strong>怎样才能让这条曲线更快进入后半段呢</strong>？</p>
<p>要做的事情太多，这里分享一些是最关键的，每一条背后都有血泪教训。</p>
<p>首先，是关于人的决策。<strong>在Forming阶段，慎重对待每一个招聘</strong>；<strong>在Norming和Performing阶段，慎重对待每一个绩效评估</strong>，在研发领域，尤其要注意绩效评估中的“Fireman Bias” -- 感谢消防英雄的人一定会比感谢用防火材料盖房子的人多，就像只有扁鹊知道他大哥的医术最高。<strong>关于人的决策就是关乎企业和团队生死存亡的决策</strong>，“生死存亡”这个词很重，这是管理学常识，却经常被忽略。Sam Altman在那次被OpenAI董事会武断裁掉后说：</p>
<blockquote>
<p>OpenAI is nothing without its people.</p>
</blockquote>
<p>其次，每个阶段的关键点要做好。在Forming阶段，需要组织一些<strong>破冰活动</strong>，形成更紧密的人际关系，也互相了解其他成员的<strong>人格特质和能力模型</strong>；同时，也要把<strong>团队目标传递给每个人</strong>，让所有人都能真正理解“Why”。这点我经验不足，以前做的很不好，有条件最好请一些专业的Facilitator，<strong>帮助团队打造一个更好的信任起点</strong>。还有一个在Forming阶段要并行开始做的是<strong>Stakeholder管理</strong>，找全利益相关方，让他们在前期就<strong>及时了解、随时参与</strong>，这对建立更大范围的信任至关重要。</p>
<p>在Storming阶段，必然产生冲突，而这时候的冲突大部分是“<strong>建设性冲突</strong>”，管理者要作为一个连接者让各方充分沟通、交换意见，<strong>直面冲突、管理冲突，而不是避免冲突</strong>。要是有冲突解决不了怎么办？<strong>找上级领导吗，错</strong>！这是一个非常好的机会，让团队成员抛开当前的问题，共同想想我们<strong>决策的准则是什么</strong>。在决策准则上达成了一致，所有的冲突拿这根尺子一量，自然有了答案，。那么，要是决策的准则也无法达成一致怎么办？那又是一个很好的机会，讨论<strong>团队的价值观是什么</strong>，大部分情况只要“以用户为中心”、“以公司整体利益为重”、“关心人的发展”这样几个普世价值观的尺子，就足以衡量出决策准则孰优孰劣了。</p>
<p>在Norming阶段，是<strong>资源调配和流程确立</strong>的关键时期，团队发展到这个阶段，有一些前期资源分配的问题就暴露出来了，树挪死、人挪活，这时调换岗位为时不晚。另外，前期经验不足埋下的风险也暴露出来了，要把每个风险视为一个改进机会，<strong>共创流程、制度，识别记录那些暂时处理不了的风险</strong>。我的经验是尽量不要管理者自己做，“微操”、“跨领域教人做事”是管理者的大忌，当好一个组织者和助推者，<strong>让团队成员充分表达意见，形成自发秩序</strong>，这就是Norming阶段的完美状态了。</p>
<p>在Performing阶段，那些曾经非常有挑战的事情，要么已经解决了、要么变成了可以轻松处理的日常事务，团队高效运转，不断输出有价值的产品和服务。在这个阶段，除了上面提到的关于人的最重要的事情“绩效和激励”以外，管理者要能看到团队文化，想办法培育独特的、符合团队发展方向的团队文化。</p>
<p>文化是一种像DNA一样，很难看到的强大力量，对提升工作满意度、保持高效状态至关重要。分享一些个人经验，我会通过一些小活动，或者表彰一些行为，来刻意打造独特的团队文化，我们的团队文化总结成了<strong>CCTL</strong>四个字母：</p>
<ul>
<li><strong>Care</strong> -- 关心用户，代码通常只是20%的工作，帮助用户端到端解决掉问题才是100%</li>
<li><strong>Collaboration</strong> -- 只做协作双赢的事情，Win-win or no deal</li>
<li><strong>Transparent</strong> -- 信任是结果，公开透明是前提</li>
<li><strong>Life-long Learning</strong> -- 终身学习，无限进步</li>
</ul>
<p>最后，作为团队领导者，在任何一个发展阶段，都要去感受团队内部的信任程度，一旦出现信任危机，不要急于下结论，就事论事、坦诚沟通，想办法恢复信任水平。<strong>团队的执行力是结果，一切效率来源于《信任的速度》</strong>。</p>
<h2 id="《相变》-创造力不能被计划" tabindex="-1">《相变》-- 创造力不能被计划 <a class="header-anchor" href="#《相变》-创造力不能被计划" aria-label="Permalink to &quot;《相变》-- 创造力不能被计划&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="从天文仪到蒸汽机" tabindex="-1">从天文仪到蒸汽机 <a class="header-anchor" href="#从天文仪到蒸汽机" aria-label="Permalink to &quot;从天文仪到蒸汽机&quot;">&ZeroWidthSpace;</a></h3>
<p>《相变》的英文标题“Loonshots”是一个生造词，对应”Moonshots“，形容像登月计划那样的疯狂项目。与登月不同的是，“Loonshots”项目不被重视、维护者被认为是“疯子”。雷达、心脏病药物、喷气式飞机、蒸汽机，这些改变世界的发明是怎么问世的呢？</p>
<p>作者借用了物理学的“相变”概念，解释这些发明怎么 在不经意中酝酿出来、突破了哪些阻碍、经历了哪些曲折，最终量变引起质变，<strong>跨过临界点一举扭转世界线</strong>。</p>
<p>这本书副标题“How to Nurture Ideas That Win Wars, Cure Diseases, and Transform Industries”已经总结全书内容，我们从中找几个有意思的案例，管中窥豹。</p>
<p>给我印象最深的是，作者用相变理论回答了“李约瑟难题”：<strong>为什么科技革命发生在欧洲</strong>？</p>
<p>一千年前的北宋已经是工业奇迹，天文、航海、金融领先欧洲将近600年，为什么没有进一步发展出科技革命和工业革命呢？</p>
<p>《相变》第9章对比了宋代沈括和500年后丹麦第谷的天文学研究，双方都得到了统治者的支持，都招募了出色的数学家助手（魏璞和开普勒），都因为统治者更替的政治因素被流放。但关键区别是：宋朝皇帝认为沈括的天文仪已经足够好了，停止了天文学的研究，而丹麦仍然继续支持第谷的研究。沈括晚年隐居山林，“只能与毛笔和砚台交流”。</p>
<p>多么可惜啊，一个伟大的项目，在萌芽、发展期被扼杀，再也没有机会达到相变的临界点。</p>
<p>另一个有趣的例子是蒸汽机的发明。1685年，在英国皇家学会，波意耳实验室的法国医生帕旁，改造了胡克制作的空气泵，两年后他出版了一本叫《Digester of Bones》的“烹饪书”，讲怎么用这个高压锅装置压碎骨头。英国皇家学会虽然没有重视这个装置，但学会提供了最早的专利机制和奇思妙想的摇篮。终于，到了1712年，纽可曼把这个装置改造成了实用的蒸汽机，跨过了相变的临界点，再后来就是大家熟知的瓦特改良蒸汽机的故事了。</p>
<p>那达成“相变”的条件是什么呢？作者也给出了答案。</p>
<p><img src="https://filecdn.code2life.top/2024_08_26_dfb72047e914b783c5f5a46e9a6afb4d5b91380623a9423aa5d1939df339f78d.png" alt=""></p>
<h3 id="创造力的摇篮-布什-韦尔平衡" tabindex="-1">创造力的摇篮：布什-韦尔平衡 <a class="header-anchor" href="#创造力的摇篮-布什-韦尔平衡" aria-label="Permalink to &quot;创造力的摇篮：布什-韦尔平衡&quot;">&ZeroWidthSpace;</a></h3>
<p>200年后的美国，AT&amp;T CEO韦尔建立的贝尔实验室里，产生了8个诺贝尔奖，点开了人类的信息革命科技树。这些创造奇迹的组织有什么共同点？作者出的答案是“<strong>布什-韦尔平衡</strong>”。在这个平衡点上，发明家自由发挥创造力、工程人员快速将技术落地。</p>
<p>具体来看，构建这样的平衡，需要把“艺术家”和“士兵”两个群体相态分离、再提供恰到好处的交流频率，让二者保持动态平衡。“艺术家”指的是有各种奇思妙想的科学家、发明家们，在宽松的环境下让“艺术家”自由发挥；“士兵”指的是带着专利解决现实问题的职业经理人、工程师、工人们；给二者提供恰到好处的交流，让双方平等的互相反馈。这种状态被称为“相态分离”。</p>
<p>为什么一定要相态分离呢？一个很明显的原因是“艺术家”是允许失败的，而“士兵”不能失败，各自的优势也完全不同。</p>
<p>为什么要有持续的交流呢？反过来想，没有足够的交流，“艺术家”的研究一定会变成空中楼阁，而失去创造增量的“士兵”一定会陷入存量竞争逐渐僵化。</p>
<p>布什-韦尔平衡，其实就是现在企业和高校都在说的“<strong>产学研融合</strong>”的最佳状态。</p>
<h3 id="无心插柳柳成荫" tabindex="-1">无心插柳柳成荫 <a class="header-anchor" href="#无心插柳柳成荫" aria-label="Permalink to &quot;无心插柳柳成荫&quot;">&ZeroWidthSpace;</a></h3>
<p>在读到这本书之前，我并没有系统性理解“布什-韦尔平衡”这个培育创新的机制。回顾以前团队成员和我自己迸发创造力的瞬间，惊讶的发现整个过程确实如此。我一直在践行谷歌80-20的做法，留出20%的时间去折腾各种新技术，我也告诉团队成员都可以花20%的时间学习自己想学的东西，哪怕和项目没什么关系。</p>
<p>我自己的一个例子是关于WebAssembly的，多年前了解到WebAssembly技术，竟然可以在JavaScript Runtime跑任何主流的编程语言，真是太酷了。虽然80%的时间都是”士兵“，留出的20%的时间，在脑海中塑造了一个“艺术家”，允许失败，肆意探索。</p>
<p>前段时间这颗种子发芽了，云计算相关的开源项目大多是Golang，如果编译成WebAssembly不就可以把编译型语言做成函数插件，随意组合使用了吗？于是，我们把Helm、Nomad都编译到了运维引擎中，也编译了一些Rust库进去，开发过程中一位同事甚至还<a href="https://go-review.googlesource.com/c/go/+/530155" target="_blank" rel="noreferrer">修复了一个Golang的Bug</a>！</p>
<p>我们团队当初那些“无用”的技术储备和算不上发明的小创新，在项目中都起到了重要的作用。如今，WebAssembly在Web后端和云计算领域的应用越来越多，这个本来为了解决“在浏览器运行旧软件”的技术，也是无心插柳柳成荫了。</p>
<p>卓越的研发团队一定<strong>不是完全循规蹈矩的、按部就班的</strong>，而是能够时不时<strong>创造出前所未有的技术和方法</strong>，轻松解决掉一些棘手的问题。创造力是团队的源头活水，也是卓越研发团队必不可少的要素之一。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>我们从《决断》、《信任的速度》、《相变》三本书出发，找到了<strong>战略目标决策、构建信任、培育布什-韦尔平衡</strong>这三个关键思想，以及卓越研发团队要具备的<strong>产品力、执行力、创造力</strong>三个要素，回顾了我们团队做的好的地方、要改进的地方。</p>
<p>Tim Pan创建的影视飓风公司有句Slogan：“无限进步”。的确，追求卓越没有止境，让我们在产品力、执行力、创造力上一直前进吧！</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件项目怎么做好技术方案选型?]]></title>
            <link>https://code2life.top/blog/0073-4-types-of-r-n-d</link>
            <guid>https://code2life.top/blog/0073-4-types-of-r-n-d</guid>
            <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件项目怎么做好技术方案选型" tabindex="-1">软件项目怎么做好技术方案选型? <a class="header-anchor" href="#软件项目怎么做好技术方案选型" aria-label="Permalink to &quot;软件项目怎么做好技术方案选型?&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">&ZeroWidthSpace;</a></h2>
<p>在研发项目技术设计阶段时，经常会遇到意见冲突。有人说“问题紧急程度高，我们要用最快的方式解决问题”；有人说“我们不能直接做，要把需求再抽象一下，这样有很多长期收益”；有的人说“现在用的技术框架XXX太落后了，最近新出来的XXX很适合我们的场景”；有人说“不要折腾了，新东西一定会踩坑，我们用成熟技术来做”......</p>
<p>团队成员多样性越强、决策过程越民主，这类争论持续时间越长。而项目初期的所有决策，都会深刻影响整个项目生命周期，一旦选型错了，很容易让项目方向陷入lock-in，<strong>错误的设计想丢丢不掉、想换换不掉</strong>，因此，技术方案的科学决策尤为重要。</p>
<h2 id="软件设计中的两类争论" tabindex="-1">软件设计中的两类争论 <a class="header-anchor" href="#软件设计中的两类争论" aria-label="Permalink to &quot;软件设计中的两类争论&quot;">&ZeroWidthSpace;</a></h2>
<p>这些年做项目的过程中发现，技术方案的争论点主要在两个方面，分别是<strong>抽象层次</strong>和<strong>技术成熟度</strong>。</p>
<h3 id="抽象层次争论-要不要再抽象一下" tabindex="-1">抽象层次争论：要不要再抽象一下? <a class="header-anchor" href="#抽象层次争论-要不要再抽象一下" aria-label="Permalink to &quot;抽象层次争论：要不要再抽象一下?&quot;">&ZeroWidthSpace;</a></h3>
<p>一种争论是<strong>抽象层次的理解不一致</strong>，具体的问题抛过来，只看这一个问题开发呢？还是把问题上下文中涉及的名词和动词泛化，抽象成更高层次的概念，用更高抽象层次的概念，解决更多同类问题呢？</p>
<p>举个例子，我们团队负责的DevOps平台曾有个需求是这样的：产线服务需要部署一个“Dogfood”环境，为了让所有业务线产品的更新，都能让公司内部员工先使用，即“吃自己的狗粮”，确保没问题再推向外部市场，并且这个环境中需要<strong>最小化流程负担，让开发能够快速发布</strong>。</p>
<p>如果不增加抽象层次，直接设计会怎么样呢？从数据库实体关系的设计看，数据库增加一张叫做“dogfood_release”的表，里面有状态字段实现定制流程。这个方案是100%匹配原始需求的，也是开发起来最快的方案。</p>
<p>如果增加一层抽象会怎么样呢？当时我的第一反应是，这个词叫“Dogfood”还是别的什么名字无所谓，需求本质上是：<strong>在一个特殊的、风险可控的产线环境里，实现近乎全自动化的发布</strong>。我们在现存的&quot;release&quot;概念基础上进行扩展，抽象出一个Auto Release Rule概念，让定制的流程能配置化，到时候给需要的应用启用这种叫“Dogfood Release”的Auto Release Rule即可。</p>
<p>可以看到，两种思路最终给用户呈现的功能几乎相同，但背后的技术方案截然不同。选哪一种更好呢？看完自然就有了答案。</p>
<h3 id="技术成熟度争论-要不要试试新技术" tabindex="-1">技术成熟度争论：要不要试试新技术？ <a class="header-anchor" href="#技术成熟度争论-要不要试试新技术" aria-label="Permalink to &quot;技术成熟度争论：要不要试试新技术？&quot;">&ZeroWidthSpace;</a></h3>
<p>技术决策的另一种典型争论是：“<strong>要不要选用XXX技术</strong>”。</p>
<p>2015年我在一家外企实习过一段时间，那家公司对新技术很开放，一些项目甚至用了Scala, Node.js这类当时还很小众的技术。他们对新技术的热情深深影响了我，随后几年我在业余时间都在学新东西, TypeScript、Golang、Kubernetes等等，甚至一度认为，新的就是好的。</p>
<p>辗转到了现在的公司，第一个挑战是解决一个复杂Node.js服务的稳定性问题。那会是2018年，我认为的Node.js已经非常成熟了，问题在于业务而不是技术，但很多人都认为Node.js仍然太新了、不稳定，哪怕我和另外两位同事，已经花了半年时间修复了所有稳定性问题，但随后几年还是被“更成熟的”Java给重写了。</p>
<p>当然，不是简单的重写，大家看到了业务扩张和技术迭代的双重潜力，决心要干一票大的。</p>
<p>我们在Java生态里的技术选型相当激进，当时微服务刚火起来，采纳了SpringCloud Netflix全套微服务方案。然而，项目越做越不对劲，遇到了大量技术上的坑，比如服务发现数据一致性问题、HTTP2调用连接莫名其妙断开问题、Hystrix/Ribbon等组件被Netflix废弃问题、SpringCloud Stream和公司的消息队列SDK难以兼容、服务拆分过细导致的DevOps复杂度爆炸等等。</p>
<p>坑踩差不多了，团队也对SpringCloud的源码读的差不多了。如果再选一次，没有人会再选SpringCloud了，也不会再拆出来十几个微服务了。</p>
<p>这个故事的教训总结成两句话就是：</p>
<ul>
<li><strong>如果缺乏新技术的人才储备，成熟的技术对团队来说也是新技术</strong>。</li>
<li><strong>如果认为“大公司用过”的新技术就已经足够成熟了，那么一定会在这些“假想的成熟技术”上吃亏</strong>。</li>
</ul>
<p>近几年还有很多方案选择上的经验教训，这些经历彻底改变了我的技术观。<strong>怎么客观定义“成熟的技术“，什么情况下可以选“新技术”</strong>? 我们需要上升到理论高度来回答这些问题。</p>
<h2 id="研发类型矩阵理论" tabindex="-1">研发类型矩阵理论 <a class="header-anchor" href="#研发类型矩阵理论" aria-label="Permalink to &quot;研发类型矩阵理论&quot;">&ZeroWidthSpace;</a></h2>
<p>上面我们用例子说明了两个维度的选择困境，一是<strong>对问题的抽象层次</strong>，二是<strong>技术成熟度</strong>。</p>
<p>我们把两个维度做成四象限，找些项目对号入座，就可以进行分类了：</p>
<p><img src="https://filecdn.code2life.top/r-n-d-type-2.png" alt=""></p>
<div style="display: none">
```mermaid
quadrantChart
    title R&D Type Matrix
    x-axis Low Maturity --> High Maturity
    y-axis Specific Solution --> Abstract Solution
    quadrant-1 Industrial R&D
    quadrant-2 Disruptive R&D
    quadrant-3 Exploratory R&D
    quadrant-4 Utility R&D
    Zed Editor: [0.3, 0.7]
    Bun: [0.2, 0.83]
    ChatGPT: [0.15, 0.9]
    PostHog: [0.7, 0.73]
    SigNoz: [0.82,0.7]
    Temporal.io: [0.8,0.9]
    Odigos.io: [0.2,0.2]
    Shoelace.style: [0.25, 0.33]
    ReactRouter: [0.75, 0.2]
    Wantwords.net: [0.85, 0.36]
```
</div>
<p>X轴是所选用技术的成熟度，左边是不成熟的新技术，右边是大量案例验证的成熟技术。比如Web Components是一个新技术，在X轴左侧，React是一个成熟技术，在X轴右侧。</p>
<p>Y轴是所要解决问题的抽象程度，下面是只解决具体问题的方案，上面是通过高度抽象的模型解决一大类问题的方案。比如UI库就是非常具体的问题，在Y轴下面，而OpenAI想做出通用人工智能（AGI）解决一切问题，抽象层次已经在Y轴高到大气层了。</p>
<ol>
<li><strong>第一象限：技术成熟度高，解决抽象问题</strong>。除了之前介绍过的Temporal，还有一个我很喜欢的项目PostHog也一样，用的技术是React/AntD/Typescript这类非常成熟的技术，但解决的问题范围巨大，想要成为Product OS，解决一切PM相关问题。这些项目像是久经沙场的将军，步步为营，却暗藏巨大的野心，我们将其称为<strong>工业型研发</strong>（Industrial R&amp;D）</li>
<li><strong>第二象限：技术成熟度低，解决抽象问题</strong>。比如前不久爆火的Bun，用的是Zig这样一个想要取代C语言的新兴编程语言，但目前版本稳定性欠佳，叫好不叫座。OpenAI做的事情更不用说了，抽象程度和技术新颖度已经超出了大多数开发者的认知。落在这个象限的大都是颠覆性项目，我们将其成为“<strong>颠覆性研发</strong>”（Disruptive R&amp;D）</li>
<li><strong>第三象限：技术成熟度低，解决具象问题</strong>。图中举了两个例子，一个是用eBPF解决Tracing问题的Odigos，另一个是用Web Component、最新Web标准的UI库Showlace。这个象限可以叫“<strong>探索型研发</strong>”（Exploratory R&amp;D）</li>
<li><strong>第四象限：技术成熟度高，解决具象问题</strong>。比如我经常用的<a href="https://wantwords.net/" target="_blank" rel="noreferrer">反向词典WantWords</a>工具，看<a href="https://github.com/thunlp/WantWords" target="_blank" rel="noreferrer">源码</a>，技术和算法很普通，但近反义词查询这个具体问题解决的很好。这个象限的项目我们称之为“<strong>工具型研发</strong>”（Utility R&amp;D）</li>
</ol>
<p>归纳完研发类型后，我们做一个反向因果的假设：<strong>因为定位成某种类型的项目，所以方案选择必须落在该象限</strong>。再找些失败的案例，可以发现这几类问题：</p>
<ul>
<li>要想做<strong>颠覆型研发</strong>，只靠成熟技术的组合，或是只解决具体问题，都达不到颠覆效果；</li>
<li>要想做<strong>工具型研发</strong>，如果过度抽象，就会脱离原始需求；</li>
<li>要想做<strong>探索型研发</strong>，继续用现成技术，团队没法更新技术储备；</li>
<li>要想做<strong>工业型研发</strong>，追求新技术会给项目带来巨大风险。</li>
</ul>
<p>一个不言而喻的结论浮出水面：<strong>应该让项目自身的定位，来决定抽象层次和技术成熟度的选择</strong>。<strong>一旦实际选择与项目定位不匹配，就会出大问题</strong>。结合其他变量，还能发现一个<strong>推论</strong>：从<strong>右下角到左上角画一个箭头，越往左上角的第二象限，项目投入越高、风险越高、收益越高、对人能力要求越高</strong>。</p>
<p>带着这个<strong>研发类型矩阵</strong>理论，我们再回到上一节的问题。</p>
<p>第一个问题是：<strong>抽象到什么程度是恰到好处的</strong>？</p>
<p>回答这个问题要回到项目的原始需求：是要做颠覆性的大事情，还是做风险可控的商业项目？是空闲时间随意的探索，还是一个迫切需要的底层工具？</p>
<p>项目定位确定了，抽象程度就很容易把控了。做小项目，小工具的时候，要警惕<strong>过度抽象</strong>，别想太多，对照原始需求做就完了；做大项目的时候，既要警惕<a href="https://en.wikipedia.org/wiki/Lazy_user_model" target="_blank" rel="noreferrer"><strong>思维惰性</strong></a>，选短期工作量最小的方式，也要抵制高抽象层次的诱惑，毕竟<strong>抽象的代价是忽略细节，而很多业务系统，核心价值就在细节中</strong>。这几年冒出来很多低代码平台，大部分是对问题过度抽象了，想造一层“银弹”出来解决所有业务问题，把工业型研发臆想成颠覆型研发，项目定位就错了。</p>
<p>第二个问题是：<strong>什么情况下能用新技术</strong>？</p>
<p>上节的故事有个蹊跷的地方：既然决定了用“成熟的Java”重构那个业务，为什么选了个Java生态里，当时并不成熟的Spring Cloud呢？</p>
<p>《How Big things get done》一书中，也描述了这个有意思的现象：<strong>越大的工程项目，越倾向于用新技术</strong>。随之也描述了一个残酷的事实：<strong>追求新技术恰恰是一些大型项目失败的主要因素</strong>。</p>
<p><a href="https://en.wikipedia.org/wiki/Diffusion_of_innovations" target="_blank" rel="noreferrer">创新扩散理论</a>告诉我们的，<strong>一群创新者会倾向于采纳另一群创新者的成果</strong>。但这不能完全解释，为什么大项目中创新扩散往往会<strong>加速</strong>。我比较信服的一种解释是：优秀的工程师会一直学新东西，这些人通常也是团队骨干、意见领袖(KOL)。大项目出现时，往往聚集了组织最优秀的人才和创新者，这群人跃跃欲试，想着怎么引入厉害的技术，在大项目中大干一番，因此大项目上马新技术的现象更明显。</p>
<p>我们理性思考一下，大项目风险本来就高，再乘以不稳定的新技术带来的风险，显然不是第一象限“<strong>工业型研发</strong>”该有的样子，而是第二象限的<strong>颠覆性研发</strong>。如果真的是第二象限的项目，需要卓越的<strong>见识、魄力、智力</strong>才能做。悉尼歌剧院成名的背后，是新技术和奇思妙想导致的1400%项目预算超支，毁掉了天才设计师乌松的整个职业生涯；ChatGPT单月增长一亿用户的背后，埋葬的是60多年来三波AI浪潮中，无数死掉的项目。</p>
<p>很多有想法的工程师，理想中是在第二象限做“<strong>颠覆性研发</strong>”，现实中是在<strong>第一象限和第四象限</strong>当将军和士兵。<strong>大部分大项目就属于第一象限</strong>，用商业公司的钱，做多数人能<strong>看得懂，用得着</strong>的软件，<strong>没有充分的评估和使用经验就引入不成熟的新技术，是不负责任且不可取的</strong>。</p>
<p>然而，只做第一象限的项目，很难提升个人和团队的技术力。微软CEO纳德拉说：“Our industry respects innovation, not tradition”。我的对策是：在主线项目之余，寻找或者创造做第三象限<strong>探索型研发</strong>的机会，把大家还认为<strong>不成熟的技术，多用多练，转化为成熟技术</strong>。</p>
<p><strong>一三象限相结合，不断提升问题抽象能力和对新技术的运用能力</strong>，那么第二象限有机会也可以去够一够了。举个例子，我们团队成员曾尝试用Rust语言重构项目底层一部分功能，效果很好，即使公司其他团队还认为Rust太新不敢用，我们也认为Rust是成熟技术了；而Service Mesh领域火爆的Istio，我们四年前开始评估、实验，但实际效果达不到预期，现在仍然将其定位为不成熟技术。</p>
<p>一句话总结现在我对新技术的态度：<strong>看的要多，学的要快，采纳要慢</strong>。</p>
<h2 id="引申思考" tabindex="-1">引申思考 <a class="header-anchor" href="#引申思考" aria-label="Permalink to &quot;引申思考&quot;">&ZeroWidthSpace;</a></h2>
<p>我们给研发矩阵加上动态发展的视角，还能得到一些有趣的启发。</p>
<h3 id="x轴-重构" tabindex="-1">X轴 - 重构 <a class="header-anchor" href="#x轴-重构" aria-label="Permalink to &quot;X轴 - 重构&quot;">&ZeroWidthSpace;</a></h3>
<p>X轴的变化，就是把项目的代码实现，换成新技术/旧技术，仍然是和原先类似的架构设计。有两种情况会在X轴改变项目定位：</p>
<ol>
<li>项目遇到技术挑战，当前方案要么用的技术太新遇到解决不了的坑，或者技术太旧实现不了某些需求。这种情况换技术方案是有价值的，谨慎评估，快速行动即可。</li>
<li>团队人员发生变化，技术栈不匹配。遇到的好几个Node.js项目被用更旧的Java重写，都是因为新接手的团队不会Node.js。<strong>这种情况对最终用户没有产生任何价值</strong>，如果是用新技术换掉过时技术可以理解，如果是反过来，只是为了统一团队技术栈放弃了新技术，明显是得不偿失的事情。</li>
</ol>
<h3 id="y轴-跃迁" tabindex="-1">Y轴 - 跃迁 <a class="header-anchor" href="#y轴-跃迁" aria-label="Permalink to &quot;Y轴 - 跃迁&quot;">&ZeroWidthSpace;</a></h3>
<p>Y轴的变化，就是整个软件的领域模型设计变了，增加或者减少了用到的抽象概念。常见的情况是范围扩大，比如从工具转为平台。由于资源限制，很多软件项目起步的时候定位就是一个工具，成功后往往会向平台级产品发展。Vercel和Hashicorp两家公司是典型的例子，做工具型研发非常成功，知名度起来后，都转向做平台级产品，讲大故事。</p>
<p>Y轴的变化通常是<strong>决定生死</strong>的，最好是在早期就用MinMax思维考虑发展路径，把问题域的底限和上限想明白，否则很容易盲目扩张到进退两难的境地。Hashicorp是一个典型的例子，两位创始人最开始只是做一些分布式系统的底层工具，Terraform、Consul、Vault这些工具迅速流行，给了Hashicorp发展成云平台的机会。然而这家看起来很成功的上市公司危机四伏，平台做的一塌糊涂，财报看不到盈利的希望，市值跌去超过80%。我在工作中也犯过类似的错误，想靠一个看起来完美的模型，统一解决所有类似的问题，忽略了<strong>生态</strong>、忽略了<strong>市场</strong>，忽略了产品的生存之本--<strong>用户价值</strong>，怎么可能只靠提升抽象层次实现<strong>工具到平台</strong>的跃迁呢？</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>我们从两类意见冲突的场景出发，归纳了<strong>研发类型矩阵</strong>：第一象限是<strong>工业型研发</strong>；第二象限是<strong>颠覆型研发</strong>；第三象限是<strong>探索型研发</strong>；第四象限是<strong>工具型研发</strong>。然后，我们又从模型中发现了“<strong>项目定位决定抽象程度和技术选型</strong>”这个深层规律，以及两个坐标轴变量与<strong>项目投入、风险、收益、人员能力要求</strong>的关系。我们也通过例子来说明了怎么运用这些规律，怎么通过一三象限项目搭配，持续磨炼工程能力。</p>
<p>最后，我们带上时间维度，思考了项目演化过程中，改变研发类型的场景和结果：<strong>换技术选型分高下，改抽象层次定生死</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[30分钟入门Kubernetes]]></title>
            <link>https://code2life.top/blog/0072-k8s-in-30-min</link>
            <guid>https://code2life.top/blog/0072-k8s-in-30-min</guid>
            <pubDate>Fri, 24 Nov 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="_30分钟了解kubernetes" tabindex="-1">30分钟了解Kubernetes <a class="header-anchor" href="#_30分钟了解kubernetes" aria-label="Permalink to &quot;30分钟了解Kubernetes&quot;">&ZeroWidthSpace;</a></h1>
<p>上周我做了一次针对业务开发团队的一次Kubernetes入门分享，目的是让大家了解迁移到Kubernetes后的服务运行环境是什么样子。</p>
<h2 id="懂pod就懂了kubernetes的一半" tabindex="-1">懂Pod就懂了Kubernetes的一半 <a class="header-anchor" href="#懂pod就懂了kubernetes的一半" aria-label="Permalink to &quot;懂Pod就懂了Kubernetes的一半&quot;">&ZeroWidthSpace;</a></h2>
<p>Kubernetes可以理解成一个对计算、网络、存储等云计算资源的抽象后的<strong>标准API服务</strong>。</p>
<p>几乎所有对Kubernetes的操作，不管是用kubectl命令行工具，还是在UI或者CD Pipeline中，都<strong>相当于在调用其REST API</strong>。</p>
<p>很多人说Kubernetes复杂，除了其本身实现架构复杂以外，还有一个原因就是里面有二十多种原生资源的API学起来曲线比较陡。但不用担心，我们只要抓住本质 -- <strong>提供容器计算能力</strong>的平台，就能纲举目张，很容易快速理解。</p>
<p>在K8S中，最重要也最基础的资源是Pod，翻译一下就是“豆荚&quot;，我们用下面这个最最基础的Nginx容器为例，搞清楚豆荚的一生，K8S就懂了一半。</p>
<p>大家也不需要研究Kubernetes怎么搭建，推荐用 <a href="https://orbstack.dev/" target="_blank" rel="noreferrer">OrbStack</a> 在本地一键安装一套Docker &amp; K8S环境出来，快速开始实验。首先，写一段这样的Yaml文件出来。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># nginx.yaml</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Pod</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  containers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">web</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">web</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          containerPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">80</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">TCP</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><p>然后，我们用kubectl把nginx的Pod创建出来，命令后面加-v8是详细日志模式，可以看出来kubectl到底做了什么事情。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pod</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nginx.yaml</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v8</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pod</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v8</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>在OpenLens或K9S等可视化工具中，我们可以看到一个叫Nginx的Pod就被”生“出来了，从kubectl的详细日志中也可以看到POST/GET等请求的信息。</p>
<div class="language-text vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>I1127 14:55:06.886901   83798 round_trippers.go:463] GET http://127.0.0.1:60649/77046cfbc5f80b52d9a1501954ee0672/api/v1/namespaces/default/pods?limit=500</span></span>
<span class="line"><span>I1127 14:55:06.886916   83798 round_trippers.go:469] Request Headers:</span></span>
<span class="line"><span>I1127 14:55:06.886921   83798 round_trippers.go:473]     User-Agent: kubectl...</span></span>
<span class="line"><span>I1127 14:55:07.166333   83798 round_trippers.go:580]     Cache-Control: no-cache, private</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>可以看到，在创建完成Pod之后，实际的Pod比我们在Yaml中声明的字段更多，这些多出来的字段就是Pod从出生后的经历证明：<strong>由调度器调度到集群可用节点、交给kubelet管理Pod生命周期、分配网络IP、挂载临时存储、容器运行时拉取镜像启动容器、控制器协调校正运行状态等等</strong>。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Pod</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  phase</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Running</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hostIP</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10.....</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  podIP</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10.....</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  conditions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Initialized</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'True'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      lastProbeTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      lastTransitionTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'2023-11-27T06:59:13Z'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Ready</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'True'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      lastProbeTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    .....</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  volumes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kube-api-access-72rkq</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      ......</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  containers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">web</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">web</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          containerPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">80</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">TCP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      resources</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      volumeMounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kube-api-access-72rkq</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          readOnly</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          mountPath</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/var/run/secrets/kubernetes.io/serviceaccount</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      terminationMessagePath</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/dev/termination-log</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      terminationMessagePolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">File</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      imagePullPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Always</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  restartPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Always</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  terminationGracePeriodSeconds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">30</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  dnsPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ClusterFirst</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  serviceAccountName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  serviceAccount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  securityContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  schedulerName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default-scheduler</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  tolerations</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">key</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">node.kubernetes.io/not-ready</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      operator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Exists</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      effect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">NoExecute</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      tolerationSeconds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">300</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">key</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">node.kubernetes.io/unreachable</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      operator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Exists</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      effect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">NoExecute</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      tolerationSeconds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">300</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  priority</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  enableServiceLinks</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  preemptionPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">PreemptLowerPriority</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br></div></div><p>展开来看，运行一个容器，必要的就是3大件，计算、网络、存储。</p>
<p>计算资源，就是CPU/Mem/GPU，是在spec的container部分声明，这个案例中没有设置到底需要多少resources，requests 和 resources.limits为空, 也就是说可能占满整个宿主机，这种情况一般叫Best-Effort QoS级别，调度优先级是比较低的。实际情况下一般会设置合理的 requests / limits，达到Burstable QoS级别或者设置requests、limits一模一样达到Guarantee级别。这个Pod经过调度器调度到某个节点之后，就会交给一个叫CRI（Container Runtime Interface）的接口，让CRI的实现来把容器真正建出来，通常是containerd, cri-o, podman, docker等等。</p>
<p>网络方面，可以看到在status里面，多出了PodIP字段，这个是调用底层一个叫CNI（Container Network Interface）的接口，让CNI的实现层给出的IP，这个过程比较复杂，涉及到一个叫pause容器的东西，入门的时候可以忽略这些细节。</p>
<p>存储方面，可以看到自动挂载了一个volume / volumeMounts, 这是对Pod挂载的额外存储，可能是配置文件或密钥，也可能是挂载一些云厂商提供的持久化存储，比如EBS、EFS盘，则会涉及到K8S第三类底层接口，CSI（Container Storage Interface，我们用的云厂商的Kubernetes发行版本一般都已经内置了CSI的实现。</p>
<p>有了计算网络存储，Pod就运行起来了，如果我们要更新Pod，可以用Update, Patch接口，但是，Pod是一个Kubernetes的<strong>原子资源</strong>，只能更新极少数字段，比如<em>image和readinessGate</em>。</p>
<p>如果想结束掉这个Pod，可以用Delete接口，来让Pod进入Terminating状态，最终被控制器删除，回收掉计算资源，容器镜像文件最终也会被GC掉。</p>
<p>这里只是讲了最浅显的流程，详细的Pod生命周期可以参考这里：<a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" target="_blank" rel="noreferrer">https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/</a>, 尤其是一些和业务强相关的生命周期活动，比如<strong>postStart并行的启动hook，preStop串行的停止hook，发送SIGTERM尝试结束进程，过了Graceful Period后发送SIGKILL信号等等</strong>，对业务很有用。</p>
<h2 id="kubernetes集群视角的计算、网络、存储" tabindex="-1">Kubernetes集群视角的计算、网络、存储 <a class="header-anchor" href="#kubernetes集群视角的计算、网络、存储" aria-label="Permalink to &quot;Kubernetes集群视角的计算、网络、存储&quot;">&ZeroWidthSpace;</a></h2>
<p>至此，我们明白了计算网络存储资源，如何赋予到Pod这个载体上。那么，计算、网络、存储的资源池本身，在Kubernetes里面叫什么呢？</p>
<p>Kubernetes集群的计算节点叫Node，和传统云平台对硬件机器的定义不同，Node也是抽象的资源，可以长出来，也可以死掉，不和运行哪个容器直接绑定，而是通过label/selector, affinity等调度相关的机制关联到Pod。</p>
<p>Kubernetes集群的块存储资源叫PersistentVolume，实际使用场景下，一般是用分布式文件系统来实现，根据业务的磁盘请求，自动创建PersistentVolume的东西叫StorageClass。</p>
<p>Kubernetes的网络是分好几层的：让整个集群变成一个大内网的Pod网络；让集群内服务互相访问自带L4负载均衡的Service网络，以及做精细流量治理的L7 Ingress/GatewayAPI/ServiceMesh网络。还有控制网络访问策略的NetworkPolicy资源。</p>
<p>首先我们看<strong>Pod网络</strong>，虽然不同CNI实现的网络原理差别巨大，但目的都是一样的，给每个Pod分配IP，并打通和其他Pod之间的路由。比如AWS就用了一个很讨巧的方式实现，直接给Pod分配当前VPC-Subnet的二级IP，DHCP和路由表都是复用的，Pod之间和现有EC2节点之间的通信方式完全一样。</p>
<p>再来看作为内部<strong>L4负载均衡器的Service网络</strong>，给每个K8S services资源分配一个虚拟IP（ClusterIP）。ClusterIP分配后，kube-proxy组件负责来实现这个虚拟IP的路由的创建和Pod Endpoint变化的实时校正。
还是以AWS EKS为例，EKS默认使用的iptables模式，kube-proxy会在每个节点上把每个ClusterIP Service的IP写入iptables，用iptables命令可以看到实现细节。</p>
<ul>
<li>由于每次变更导致的iptables修改，大规模集群用K8S内置的Service负载均衡是存在性能问题的，切换到ipvs模式可以解决；</li>
<li>还有一种没有ClusterIP的Headless Service，借助DNS实现了端点自动发现，不是常规的L4负载均衡</li>
<li>如果需要直接把某个Service暴露到公网去，还有NodePort/LoadBalancer类型的Service，kube-proxy会在集群每个节点listen NodePort端口，iptables写入NodePort对应的DNAT规则</li>
<li>LoadBalancer / NodePort类型的Service还有一个关键字段'externalTrafficPolicy'，简单理解是跨节点负载均衡模式还是本地节点直连模式，跨节点负载均衡还会引起外部LB的健康检查失效问题以及内部服务无法获取Client IP问题，这些都是平台方需要处理好的，不需要业务方关心，业务团队记住一个原则，永远不要用NodePort Service就行。</li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># https://zhuanlan.zhihu.com/p/196393839</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Chain OUTPUT (policy ACCEPT)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># target     prot opt source               destination         </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># KUBE-PROXY-FIREWALL  all  --  anywhere             anywhere             ctstate NEW /* kubernetes load balancer firewall */</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># KUBE-FIREWALL  all  --  anywhere             anywhere          </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nat</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Chain KUBE-SVC-TCOU7JCQXEZGVUNU (1 references)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#target     prot opt source               destination         </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#KUBE-SEP-HI2KQBDGYW5OVKWN  all  --  anywhere             anywhere             /* kube-system/kube-dns:dns -> 10.52.xx.xx:53 */ statistic mode random probability 0.50000000000</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#KUBE-SEP-XQT5TF2PMBOMEGDC  all  --  anywhere             anywhere             /* kube-system/kube-dns:dns -> 10.52.xx.xx:53 */</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>最后了解一下L7服务网络，一般由类似Nginx Ingress / Envoy 之类的应用流量负载均衡器提供，对于业务来说，就当把nginx conf拆成一个一个yaml片段就好。Ingress直管南北向流量，而Service Mesh把东西南边向流量全托管了，也能实现一些比Nginx conf里面更复杂的行为，比如流量加密、鉴权、故障注入、熔断降级、重试等等。</p>
<h2 id="其他原生资源要么是对pod套娃、要么是打辅助的" tabindex="-1">其他原生资源要么是对Pod套娃、要么是打辅助的 <a class="header-anchor" href="#其他原生资源要么是对pod套娃、要么是打辅助的" aria-label="Permalink to &quot;其他原生资源要么是对Pod套娃、要么是打辅助的&quot;">&ZeroWidthSpace;</a></h2>
<p>Kubernetes的API设计非常符合<strong>单一职责原则</strong>（SRP），Pod就是一个包容器的单纯的豆荚，delete了就没了。</p>
<p>但是，你要跑服务怎么办？没关系，单一职责原则下，实现新功能就是一个套娃，Kubernetes抽象了一个叫Deployment套住一个叫ReplicaSet的东西，ReplicaSet再来套住Pod。
这样，Deployment只管变更处理轮换RS，RS只管保证有n个pod在跑，Pod没了再生，死了重启，这里也体现了Erlang典型的let it crash的思维。</p>
<p>哪天你说要训练一个AI模型干掉OpenAI，Kubernetes给你提供了一个叫CronJob和Job的抽象，CronJob套住了Job，Job又套住了Pod。Job只管一次性run的东西，要重试几次，跑完多久删Pod这些事，Cronjob则是一个天然的分布式cron，只管定时把job这个东西生出来。CronJob和Job广泛用在大数据处理管线、CICD管线，AI训练这些领域。OpenAI也是一个包含8000多个节点的巨大Kubernetes集群训练出来的。</p>
<p>哪天你又想用Kubernetes运行一个数据库，恭喜头铁的你，学到了最复杂的一种原生资源，StatefulSet，deployment是把豆荚当牲畜，想杀就杀，StatefulSet是把Pod当宠物，宠物不好养的，每个Pod都不能随便动，更新的时候只能按序一个一个更新。</p>
<p>除了这些对Pod套娃的资源，剩下的可以理解成打辅助的，比如把流量引入集群的Ingress，内部流量负载均衡的Service，给每个Pod提供的分布式配置ConfigMap、分布式密钥存储Secret。还有一些策略控制和资源限额的辅助类，这里不一一展开了。</p>
<h2 id="custom-resource-kubernetes变成超纲题的源头" tabindex="-1">Custom Resource - Kubernetes变成超纲题的源头 <a class="header-anchor" href="#custom-resource-kubernetes变成超纲题的源头" aria-label="Permalink to &quot;Custom Resource - Kubernetes变成超纲题的源头&quot;">&ZeroWidthSpace;</a></h2>
<p>说完了Kubernetes原生资源类型的分类、用法，剩下的就是非原生资源了，或者说叫Custom Resource。Custom Resource提供了一个无限想象的扩展点，社区有个Operator Framework专门用来开发Kubernetes的扩展资源。</p>
<p>搞云原生的没有几个没开发过几个Custom Resource，这种人人都能写扩展的机制，直接把Kubernetes这个本来就有难度的东西整成了超纲题。</p>
<p>其中有一些典型的成功案例，比如应用非常广泛的Prometheus Operator，在集群里扩展了ServiceMonitor，PrometheusRules这些资源，可以用Yaml非常方便动态地定义监控告警，也可以通过写Prometheus,AlertManager Yaml资源一键部署Prometheus集群实例。</p>
<p>社区还有一个项目叫CrossPlane, 把Kubernetes原教旨主义发挥到了极致，这个项目把所有AWS/GCP/Azure的资源，全部给Kubernetes Yaml化了，部署一个RDS、ElastiCache集群，就是声明一个RDS、ElastiCache的Custom Resource Yaml，Kubernetes既是云资源状态数据库也是云资源的部署脚本，再也不用写Terraform或者到AWS Console上吭哧点击了。</p>
<p>我们也做过的一个非常简单的自定义资源，CronDaemonJob，用来在每台机器上像DaemonSet一样跑Cron Job, 用来自动更新OS patch、自动清理应用归档的日志防止磁盘刷爆，简单来说，这个资源可以用来当Kubernetes里面的ansible。</p>
<h2 id="重新思考kubernetes是什么" tabindex="-1">重新思考Kubernetes是什么 <a class="header-anchor" href="#重新思考kubernetes是什么" aria-label="Permalink to &quot;重新思考Kubernetes是什么&quot;">&ZeroWidthSpace;</a></h2>
<p>到这里，我们大概搞清楚了Kubernetes对于使用者来说意味着什么。从Kubernetes自身的组件视图来看包括这些东西：</p>
<ul>
<li>每个机器装一个叫kubelet的Agent，控制这台机器运行什么</li>
<li>每个机器装一个kube proxy的东西用来托管网络防火墙规则，并装一个CNI的实现，控制集群内部的Pod IP分配和网络路由</li>
<li>可选的，装一个CSI的实现，接管持久化存储盘的创建和挂载</li>
<li>这些东西都听API Server + Controller Manager + Scheduler 组成的控制中心，这套控制中心暴露一套标准的可扩展的REST API，数据全部存到了ETCD元数据集群里。</li>
</ul>
<p>，让我们操作分布式集群，再也不用撸shell命令，一切命令都API化，一切资源都变成了ETCD的数据记录。</p>
<p>了解了这些，也就明白了<strong>Kubernetes本质上是对现有技术的封装，形成了一套云资源操作系统</strong>，真正干活的还是服务器上的进程而已，真正对资源做隔离的还是cgroup和namespace这些linux内核原有的东西。</p>
<p>了解了这些，也就明白了，为什么Kubernetes挂了不影响正在跑的服务？为什么在Kubernetes集群做应用性能调优，还是去看EC2用什么instance type，PV存储是哪一代的EBS、EFS，还是去看Subnet内核VPC之间怎么优化RTT延迟和提升带宽？</p>
<p><strong>Kubernetes包含了分布式集群的一切，Kubernetes又一无所有</strong>。</p>
<h2 id="kubernetes的a-b面" tabindex="-1">Kubernetes的A/B面 <a class="header-anchor" href="#kubernetes的a-b面" aria-label="Permalink to &quot;Kubernetes的A/B面&quot;">&ZeroWidthSpace;</a></h2>
<p>Kubernetes带来的最大的几个好处，分别是标准化、弹性、可扩展。</p>
<p>REST API带来的管理界面完全标准化，</p>
<p>存个ETCD记录就创建或校正资源状态带来了极致弹性，扩缩容就在弹指之间，</p>
<p>开发一个自定义资源就实现任意功能带来了丰富的可扩展性，演化出庞大的<a href="https://landscape.cncf.io/" target="_blank" rel="noreferrer">CloudNative生态系统</a>。</p>
<p>既要又要还要，标准，弹性，可扩展，都有了，看起来很完美，但任何事物都有两面，Kubernetes的这些好处，也是坏处的根源。</p>
<p><strong>标准化的暗面是复杂化</strong>。标准要考虑到所有情况，所以这个标准不可能简单，仅仅是一个Pod的spec，就有几十个字段，可能一小半字段大部分人都没有见过。Kubernetes学透的难度和自建运维难度，从一堆培训考证机构可见一斑。即使是大公司，也最好不要有自建Kubernetes的念头，Kubernetes自己的几个组件每个都有<a href="https://kubernetes.io/docs/reference/config-api/" target="_blank" rel="noreferrer">上百个启动参数</a>，要么是不懂坑有多大的年轻人，要么是假装整明白的人，要么是身在云厂商里面真正懂的人。</p>
<p><strong>弹性的暗面是易失性</strong>，连集群的Node能随时长出、随时消逝，带来了对应用架构的侵入，在Kubernetes中运行的有状态服务必须具备动态发现的能力，想在代码中配置静态IP的时代结束了。我还发现一个Kubernetes带来的效应，”日志丢失焦虑“，在VM上大概没有人会担心日志没采集到，Kubernetes上Pod飘来飘去，总有人问，服务挂掉前的最后一行日志怎么采的，采不到怎么办？</p>
<p><strong>可扩展的暗面是良莠不齐</strong>，在CNCF Landscape和开源社区的并不是都是优秀的产品，甚至有些问题很大的东西也流行起来。比如之前团队有位同事仔细读过Click house operator的代码，这个1.5K star的项目，代码质量可能在及格线以下。4年前和另一位同事尝试用ES Operator和Kafka Helm Chart来运维ES/Kafka，当时成熟度还远没有达到生产可用的水平。另外，Helm这个Kubernetes最流行的包管理工具，也是”worse is better“的典型代表，Helm作者哲学家Matt Butcher提出OAM的思想后，自己去搞&quot;下一代云计算&quot;WASM生态去了。</p>
<p><strong><a href="https://medium.com/@mbianchidev/2023-devops-is-terrible-ec88162c86d7" target="_blank" rel="noreferrer">复杂，是成长的代价</a></strong>。</p>
<p>最后以一张经典动图结束本文。</p>
<p><img src="https://miro.medium.com/v2/resize:fit:996/format:webp/1*EdnOhdQ9vIoWMc67tElgDw.gif" alt=""></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[如何理解工程师的跨文化差异]]></title>
            <link>https://code2life.top/blog/0071-engineer-cross-culture</link>
            <guid>https://code2life.top/blog/0071-engineer-cross-culture</guid>
            <pubDate>Tue, 30 May 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="如何理解工程师的跨文化差异" tabindex="-1">如何理解工程师的跨文化差异 <a class="header-anchor" href="#如何理解工程师的跨文化差异" aria-label="Permalink to &quot;如何理解工程师的跨文化差异&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">&ZeroWidthSpace;</a></h2>
<p>做出好的软件产品，需要许多不同种类的工程师密切协作，高频沟通。跨国软件企业一般会在<strong>不同的地区招聘不同的岗位</strong>，享受不同地区<strong>某些技能的人才密度优势</strong>，比如：美国硅谷的产品工程师和设计师、印度班加罗尔的运维工程师和技术支持工程师、中国的开发和测试工程师。于此同时，不同岗位跨了<strong>多个时区</strong>和<strong>文化背景</strong>，公司也必然付出<strong>沟通效率降低</strong>和<strong>信任成本变高</strong>的代价。</p>
<p>这篇文章从打交道比较多的<strong>中、美、印工程师出发，谈谈背后的跨文化差异</strong>，目的是让<strong>我们更好的理解跨文化</strong>，但不要先入为主，给每个独一无二的人贴上标签简单归类。</p>
<p>由于片面的个人经验的局限性，缺少大量样本验证，这篇文章比较主观，想了解更多的跨文化差异，推荐阅读这本书: <a href="https://book.douban.com/subject/25911254/" target="_blank" rel="noreferrer">《The Culture Map》</a> 以及这两篇书评：</p>
<ul>
<li><a href="https://book.douban.com/review/12496170/" target="_blank" rel="noreferrer">跨文化沟通差异背后的启发</a></li>
<li><a href="https://book.douban.com/review/12585432/" target="_blank" rel="noreferrer">多几眼看『跨文化冲突』</a></li>
</ul>
<h2 id="先想清楚-vs-先做试试" tabindex="-1">先想清楚 vs 先做试试 <a class="header-anchor" href="#先想清楚-vs-先做试试" aria-label="Permalink to &quot;先想清楚 vs 先做试试&quot;">&ZeroWidthSpace;</a></h2>
<p>以前最常遇到的一个跨文化冲突的例子是，中美印的工程师对&quot;right things&quot;的理解不一致。</p>
<p>不管人在哪里，遇到的大多数工程师都是积极做事的，但不同文化和思维模式下，对&quot;Do things right&quot;和&quot;Do right things&quot;的重要性排序是不一样的。</p>
<p>美国的工程师会说：</p>
<blockquote>
<p>Let's do right things at first place.</p>
</blockquote>
<p>中国和印度的工程师会说：</p>
<blockquote>
<p>Do it at first, evolve progressively.</p>
</blockquote>
<p>美国的工程师喜欢讨论Why的问题，什么是正确的事情，中印的工程师喜欢讨论How的问题，怎么把事情往下做。如果要开始一个中型的软件项目，需求已经明确的情况下，<strong>架构设计到什么程度再开始做</strong>，中美印工程师给出的答案是不一样的：</p>
<ul>
<li>美国的工程师：至少得有1个月才能设计好</li>
<li>中国的工程师：得一周设计完再开始</li>
<li>印度的工程师：找个参考项目，直接开始写吧</li>
</ul>
<p>美国的的战略型选手相对多一些，更善于制定中长期计划和方向；中印的战术型选手相对多一些，更善于随机应变。虽然所有人都赞同应该持续迭代，但计划到什么程度才能开始，抽象层级要到什么程度，设计是要更“理想化”还是“实际的解决当下问题”，不同文化的工程师是很难达成一致的。这也一定程度造成了国内工程师效率比美国高很多，但做出0到1创新的事情相对较少。</p>
<p>另一个有意思的现象是，不论中美，工程执念比较深，到最后都会有完美主义倾向。美国工程师更较真，坚持自己的想法和方向，很难说服他们放弃一个高度抽象的“正确的方向”，转向一个更可行的方案；一些中国工程师对技术的执念很深，会不断去优化一些技术参数，小到一个线程池的大小，大到HuggingFace上华裔们卷出来的一大堆效果相近的AI模型。而通常印度裔工程师对工程技术和方向的执念都不深，方向错了可以换，遇到问题解决问题，不容易死钻牛角尖。</p>
<h2 id="一阶思维、二阶思维" tabindex="-1">一阶思维、二阶思维 <a class="header-anchor" href="#一阶思维、二阶思维" aria-label="Permalink to &quot;一阶思维、二阶思维&quot;">&ZeroWidthSpace;</a></h2>
<p>对于上面的现象，一个解释是发展阶段不同，导致思维模式不同：对于发展中国家，变化大于计划是常事，“随机应变”是一种更加实用的能力；而发达国家，更强调守时、行为准则、一致性、按计划行事。</p>
<p>我更赞同的解释是一位朋友从教育层面的分析，不同的发展阶段，要求国家采用不同的教育体系，发展中国家注重培养<strong>一阶思维</strong>，目标是能做事情，发达国家注重培养<strong>二阶思维</strong>，目标是能想事情。相比于历史沉淀下来的文化的影响，教育的影响会体现在当代文化中，也反映在不同国家和族裔的竞争优势上。</p>
<p>那么，什么是<strong>一阶思维和二阶思维呢</strong>？</p>
<p>有一个对认知能力的分类的经典理论：<a href="https://zhuanlan.zhihu.com/p/165154137" target="_blank" rel="noreferrer">布卢姆分类法</a>。认知能力从底层到高层，分别是记忆、理解、应用、分析、评估、创造，下面三层是<strong>记忆、理解、应用</strong>，归类为低阶思维，上面三层<strong>分析、评估、创造</strong>，归类为高阶思维。</p>
<p><img src="https://filecdn.code2life.top/1st-2nd-order-mindset.png" alt=""></p>
<p>“高低”很容易让人产生误解，但并不是说高阶思维比低阶思维更厉害，只是所在的层级不一样，就像高级编程语言和低级编程语言的区别一样。所以，叫<strong>一阶思维</strong>和<strong>二阶思维</strong>可能更准确，就像数学中的一阶导数、二阶导数。</p>
<p>国内很多人都经历过应试教育，一定感受过，<strong>中国教育模式偏向记忆、理解、应用</strong>，注重一阶思维，往坏了说，是培养了做题家和打工人，换个好的说法，是培养了基础牢固、应用和实践能力强的优秀人才。反观<strong>欧美教育，偏向分析、评估、创造</strong>，注重二阶思维，轻视了一阶思维，和国内的教育体系正好相反。</p>
<p>那位朋友说，美国的产品经理+中国工程师的组合出来的东西，比美国的产品经理+美国工程师，或者中国的产品经理+中国工程师的效果都好。用教育目标的差异就很容易解释。</p>
<p>欧美教育体系加持下的分析评估能力，会让产品的成型率更高；中国教育体系加持下的应用能力，会让产品的实现和交付效果更好。</p>
<p>美国的开发工程师在一阶思维上比较相对薄弱，有一些同事会被抱怨写代码效率低，一些基础的东西不会写。</p>
<p>中国的产品工程师具备二阶思维的并不多，经常有产品经理来回改需求的故事，其实就是前期分析评估能力不够，产品定位不准。但中国的开发工程师的确非常猛，看看我们每个日常使用App里，产品经理塞了几百个没用的功能，神奇的是，这些功能全都给开发出来了。</p>
<p>印度的工程师<strong>区间很宽</strong>，擅长一阶思维和二阶思维的都有。历史上，印度的成分就非常复杂，由于经常被四面八方的帝国入侵，形成了大融合的局面，超过1万人使用的语言就有121种，另外从事工程师岗位的人，不少是在印度长大，又到发达国家读大学或者工作，接触过多种文化。但在印度本土的工程师，和中国类似，仍然是以一阶思维为主的。</p>
<p>我们从企业整体来看，<strong>基层岗位需要战术型选手、高层岗位需要战略型选手</strong>，中国的工程师普遍擅长基层岗位、欧美的工程师和经理相对更擅长高层岗位，印度的工程师区间宽，各层岗位都能找到人，这是和各自族裔擅长的思维层级的一致的。华人在硅谷做到&quot;C-suite&quot;(CEO/COO/CFO这类executive-level岗位）的凤毛麟角，这并不能归因于歧视，经济学的一个常识是，<strong>市场竞争会减弱甚至消除歧视</strong>，更多要归因于教育体系影响下的思维层次差异。</p>
<p>再延伸思考，我们要怎么办呢？中国的教育体系，欠缺对二阶思维的培养，而教育改革一定是滞后的，对于每个人是等不起的，怎么走思维模式的升迁之路，是对我们个人发展极其重要的话题。</p>
<p>那位朋友的观点是，大部分二阶思维，都是从下面三层的一阶思维发展出来的，一阶思维的学习和工作是必要的，但到达一定阶段之后，到了更需要二阶思维的学习认知层级或者工作岗位，会感觉到苦痛和无所适从，这时就是蜕变的机会。他给我的建议是：聚焦到&quot;Why&quot;上，不要轻易滑向&quot;How&quot;，搞清楚why才是二阶思维的目的。</p>
<p>朋友的建议对我最近两年的思维升级起到了非常大的作用，遇到的每一件有挑战的工作，我开始尝试用<a href="https://en.wikipedia.org/wiki/Five_whys" target="_blank" rel="noreferrer">5 Why原则</a>分析问题，不停往下追问下一层的Why，找到根本原因；再把问题拆分多个维度，评估多种方案的优缺点；最后再决策哪种方案更合适。当这些二阶思维模式，建立在超过1万小时编程沉淀的一阶思维上的时候，会越来越发现，分析评估起来更加结构化；思考高阶问题时，不再去纠结于某一行代码怎么写；在方案落地时，也不失执行力。</p>
<p>在对孩子的教育上，也同样实践了What -&gt; Why -&gt; How的原则，先培养一阶思维，再培养二阶思维。比如，孩子背诗的时候，一定会解释诗的含义是什么，作者当时在什么场景下，为什么写这首诗；孩子在搭机械积木的时候，解释为什么电机能转动、为什么连杆结构可以改变力的方向。追问Why了解原理，就进入了“分析”这个认知层级；了解了Why后提升了“品味”，看到多个类似的事物给出评价，就进入了“评估”这个认知层级；最终才能进入“创造”的层次，能发现和创造出真正的与众不同的规律和事物。</p>
<p>身边有不少同事把孩子送到美国读书，认为欧美教育优于中国教育，而我认为是各有优劣的，国内的STEM教育强于欧美，但缺乏启发式教育和因人而异的培养。分析教育体系优劣的时候，也可以运用下二阶思维，去深入分析、评估每个学校和老师，家长如果是带着”鸡娃“的思路，人云亦云，那在哪读都差不多。</p>
<h2 id="椰子、桃子、咖喱" tabindex="-1">椰子、桃子、咖喱 <a class="header-anchor" href="#椰子、桃子、咖喱" aria-label="Permalink to &quot;椰子、桃子、咖喱&quot;">&ZeroWidthSpace;</a></h2>
<p>美国有个笑话，说“一个美国人和中国人创建了一个公司，最后印度人把这个公司接管了”</p>
<p>对于这句话前半部分，有教育体系的因素，华人擅长的一阶思维和欧美人擅长的二阶思维互补，做成事的概率更大；对于后半部分，为什么印度人最终把公司接管了，网上流传的解释是，“印度人喜欢抱团，互相抬上去了”，这个解释有贬低印度裔的倾向，而我遇到的印度同事，不乏优秀的工程师和经理，“抱团互抬”只是一个片面的、浅显的现象。</p>
<p>上一节我们分析了教育体系不同所影响的当代文化，这一节，我们再深一层看，寻找那些从<strong>历史沉淀下来的，更悠久的文化差异</strong>，这些差异导致了不同<strong>性格特质</strong>的人概率分布不同，也可以部分解释这句话。</p>
<p>比如在社交方面，有一组形象的比喻：</p>
<ul>
<li>华裔<strong>椰子型人格</strong>比较多，外壳坚硬，打破这个壳之后很容易形成坚固长久的友谊，熟人关系网络更强；</li>
<li>欧美裔<strong>桃子型人格</strong>比较多，外面柔软，陌生人之间也很热情友善，但友好并不等于友情，更偏向于陌生人社会网络；</li>
<li>印度裔成分复杂，更像<strong>咖喱</strong>，虽然民族之间的价值观和宗教差异很大，但更乐于沟通和分享、给别人反馈，做事的时候很容易互相融合。</li>
</ul>
<p>东亚在儒家文化影响下，倾向于建立少而紧的连接，更容易获得对团队和集体的归属感。美国文化则相反，而遇到的欧美的同事，能感觉到自古以来个人主义思想的影响，注重自我发展，偶尔会一对一互相聊一些反馈。</p>
<p>南亚文化对人与人的连接很注重，他们喜欢做冥想这种“精神瑜伽”，追求和整个世界的连接，有印度教的&quot;梵我合一&quot;、佛教的&quot;破除我执&quot;的哲学思想在里面。印度人似乎对中国人在意的“人情”没有太多概念，我多次遇到印度的同事询问能否给他的经理写一些工作评价、晋升推荐，一开始很意外，后来也习惯了。</p>
<p>这些不同文化下不同区间的人格类型，宏观的表现是整体上熟人社会还是陌生人社会；大多数人是信奉“Business is Personal”还是“Business is Business”；是先培养好社交关系逐步建立信任，再谈事情；还是通过共同的利益、目标、合约来快速建立信任。反映到市场上宏观体感是：在国内做生意要靠人之间的关系，在欧美市场更多靠产品本身。</p>
<p>不过在企业文化比较健康的<strong>软件工程师</strong>这个圈子内，通常信息公开程度高，不论是哪个国家哪个族裔，哪种性格特质，更多的还是靠共同的目标、角色职责划分来建立信任，快速进入合作状态，个人关系和工作关系分的比较开，所以“拉关系”对工作的作用不大。</p>
<p>这个比喻对性格和社交方面的分析比较笼统，在《The Culture Map》这本书里有更系统的分析，书中把不同的族裔的文化放到<a href="https://book.douban.com/review/13940677/" target="_blank" rel="noreferrer">8个维度</a>来看：</p>
<ul>
<li>Communication: low-context / high-context</li>
<li>Evaluation: directive negative feedback / indirect negative feedback</li>
<li>Persuading: principle-based persuading / application-based persuading</li>
<li>Leading: egalitarian / hierarchical</li>
<li>Deciding: consensual / top-down</li>
<li>Trusting: task-based trust /relationship-based trust</li>
<li>Disagreeing: confrontational /avoids confrontation</li>
<li>Scheduling: linear-time /flexible time</li>
</ul>
<p>和不同族裔的让打交道，感受最明显的是Communication和Evaluation两个维度，即沟通交流、评估反馈，用这两个维度构建4个象限，书中把不同的文化分到了这4个象限：</p>
<p><img src="https://filecdn.code2life.top/cross-culture-communication-map.png-ImageProcess" alt=""></p>
<p>可以看出，不管是东亚还是南亚文化，在沟通维度，亚裔通常是“高语境(High-context)”的，沟通过程的隐式信息较多，欧美相对会更明确，没有那么多“弦外之音”，美国是“低语境(Low-context)”的，文化层面就排斥隐式信息，倾向于直说。</p>
<p>而评估和反馈这个维度，中美印都是类似的，倾向于给间接的负面反馈，批评的时候会“玻璃碴里找糖”，通常先给一段积极肯定的反馈，再接上负面反馈和改进意见，最后可能还会以积极肯定的话结束，这就是批评的“三明治原则”。如果是正常谈事情的“btw(by the way)”，就是真的是顺便一说的<strong>次要信息</strong>，如果是给你提改进建议时的&quot;btw&quot;，其实是<strong>主要信息</strong>。</p>
<p>但在表达负面评价和拒绝的委婉程度上，东亚文化对反对意见更敏感，因此很多国内的经理最怕批评人，一个原因是熟人社会里对“面子”的执着；另一个原因是中国的文化并不完全接受“就事论事”，记得之前组织行为学老师说：“不存在完全的就事论事，事情背后是人，批评一定要私下说，表扬一定要公开说”。这点在美国文化也是部分适用的，但在对于人在美国的一些欧洲人就不适用了。</p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>《易经》中说:“<strong>观乎天文，以察时变，观乎人文，以化成天下</strong>”。保持开放包容的心态，感受不同的文化，不仅对工作有帮助，而且也是一种乐趣。</p>
<p>遇到一个较真的人，不需要贴上贬义的“固执”，遇到一个含蓄的人，不需要贴上恶意的“虚伪”。理解了这些文化之间差异、人与人的差异，自然会减弱偏见，看到更大的世界。</p>
<p>每个文化底子里都有人性共同的优点，<strong>每个人都是相似的，每个人又彼此不同</strong>。不要带着框架去预判，<strong>带着这种知觉，消除误解，尽可能去了解每一个人</strong>。</p>
<blockquote>
<p>We are all the same, We are all the different. -- Erin Meyer</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[工作流引擎Temporal学习笔记]]></title>
            <link>https://code2life.top/blog/0070-temporal-notes</link>
            <guid>https://code2life.top/blog/0070-temporal-notes</guid>
            <pubDate>Mon, 23 Jan 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="工作流引擎temporal学习笔记" tabindex="-1">工作流引擎Temporal学习笔记 <a class="header-anchor" href="#工作流引擎temporal学习笔记" aria-label="Permalink to &quot;工作流引擎Temporal学习笔记&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h2>
<nav class="table-of-contents"><ul><li><a href="#目录">目录</a></li><li><a href="#temporal简介">Temporal简介</a></li><li><a href="#_5分钟上手temporal">5分钟上手Temporal</a></li><li><a href="#实践与思考">实践与思考</a></li></ul></nav>
<h2 id="temporal简介" tabindex="-1">Temporal简介 <a class="header-anchor" href="#temporal简介" aria-label="Permalink to &quot;Temporal简介&quot;">&ZeroWidthSpace;</a></h2>
<p>Temporal是一个新兴的分布式的工作流引擎。如果你在工作中遇到过以下这些场景，都可以来了解一下Temporal这个底层引擎。
（长文预警，建议分次服用）</p>
<ol>
<li>跨服务、跨时间周期的复杂业务流程</li>
<li>业务工作流建模（BPM）</li>
<li>DevOps工作流</li>
<li>Saga分布式事务</li>
<li>BigData数据处理和分析Pipeline</li>
<li>Serverless<a href="https://github.com/serverlessworkflow/specification" target="_blank" rel="noreferrer">函数编排</a></li>
</ol>
<p>这些场景看上去互相没有太大关联，但有一个共同点：需要<strong>编排</strong>（Orchestration）。</p>
<p>Temporal解决的关键痛点，就是<strong>分布式系统中的编排问题</strong>。</p>
<h4 id="编排的本质是什么" tabindex="-1">编排的本质是什么？ <a class="header-anchor" href="#编排的本质是什么" aria-label="Permalink to &quot;编排的本质是什么？&quot;">&ZeroWidthSpace;</a></h4>
<p>要理解编排，可以借助和Orchestration对应的另一个概念：Choreography。找不到合适的中文翻译，还是看图理解吧：</p>
<p><img src="https://filecdn.code2life.top/orches_vs_choreo.png" alt=""></p>
<p>举个例子，我们开发微服务时，经常借助消息队列（MQ）做事件驱动的业务逻辑，实现最终一致的、跨多个服务的数据流，这属于Choreography。而一旦引入了MQ，可能会遇到下面一系列问题：</p>
<ul>
<li>消息时序问题</li>
<li>重试幂等问题</li>
<li>事件和消息链路追踪问题</li>
<li>业务逻辑过于分散的问题</li>
<li>数据已经不一致的校正对账问题</li>
<li>...</li>
</ul>
<p>在复杂微服务系统中，MQ是一个很有用的组件，但MQ不是银弹，这些问题经历过的人会懂。如果过度依赖类似MQ的方案事件驱动，但又没有足够强大的消息治理方案，整个分布式系统将嘈杂不堪，难以维护。</p>
<p>如果转换思路，找一个“调度主体”，让所有消息的流转，都由这个&quot;指挥家&quot;来控制怎么样呢？对，这就是Orchestration的含义。</p>
<ul>
<li><strong>Choreography</strong> 是无界上下文，去中心化，每个组件只关注和发布自己的事件，完全异步，<strong>注重的是解耦</strong>；</li>
<li><strong>Orchestration</strong> 是有界上下文，存在全局编排者，从全局建模成状态机，<strong>注重的是内聚</strong>。</li>
</ul>
<p><strong>Temporal的所有应用场景，都是有全局上下文、高内聚的「编排」场景</strong>。比如BPM有明确的流程图，DevOps和BigData Pipeline有明确的DAG，长活事务有明确的执行和补偿流程。</p>
<p>Temporal让我们像写正常的代码一样，可以写一段工作流代码，但并不一定是在本机执行，哪一行在什么时间yield，由服务端信令统一控制，很多分布式系统韧性问题也被封装掉了，比如，分布式锁、宕机导致的重试失败、过期重试导致的数据错误，并发消息的处理时间差问题等等。</p>
<h4 id="temporal关键概念" tabindex="-1">Temporal关键概念 <a class="header-anchor" href="#temporal关键概念" aria-label="Permalink to &quot;Temporal关键概念&quot;">&ZeroWidthSpace;</a></h4>
<ol>
<li><strong>Workflow</strong>，Workflow是在编排层的关键概念，每种类型是注册到服务端的一个WorkflowType，每个WorkflowType可以创建任意多的运行实例，即WorkflowExecution，每个Execution有唯一的WorkflowID，如果是Cron/Continue-as-New, 每次执行还会有唯一的RunID。Workflow可以有环，可以嵌套子工作流（ChildWorkflow）；</li>
<li><strong>Activity</strong>，Workflow所编排的对象主要就是Activity，编排Activity就行正常写代码一样，可以用if / for 甚至 while(true) 等各种逻辑结构来调用Activity方法，只要具备确定性即可；</li>
<li><strong>Signal</strong>，对于正在运行的WorkflowExecution，可以发送携带参数的信号，Workflow中可以等待或根据条件处理信号，动态控制工作流的执行逻辑。</li>
</ol>
<p>下图是Temporal Dashboard中一个Workflow的执行详情示例。</p>
<p><img src="https://filecdn.code2life.top/temporal_sample.jpg" alt=""></p>
<h2 id="_5分钟上手temporal" tabindex="-1">5分钟上手Temporal <a class="header-anchor" href="#_5分钟上手temporal" aria-label="Permalink to &quot;5分钟上手Temporal&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="搭环境" tabindex="-1">搭环境 <a class="header-anchor" href="#搭环境" aria-label="Permalink to &quot;搭环境&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>方法1：本地运行Temporalite</strong></p>
<p>本地调试一般没有性能和稳定性要求，建议下载运行All in one的Binary：<a href="https://github.com/temporalio/temporalite" target="_blank" rel="noreferrer">Temporalite</a>。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">temporalite</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> start</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --namespace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> default</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>下载Binary放到Path后，一行命令启动就能连localhost 7233端口使用Temporal服务了，也可以打开浏览器进入Dashboard查看运行状态：<a href="http://127.0.0.1:8233" target="_blank" rel="noreferrer">http://127.0.0.1:8233</a></p>
<p><strong>方法2：开发或产线环境Helm部署分布式Temporal</strong></p>
<p>Dev/Prod环境建议用<a href="https://github.com/temporalio/helm-charts" target="_blank" rel="noreferrer">Helm + Kubernetes</a>部署，存储层准备独立运维的MySQL或PostgreSQL。</p>
<p>提前跑create database temporal &amp; temporal_visibility命令创建好数据库，等Helm install完成后，通过admintools进去初始化数据库Schema：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependency</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # optional</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> values/values.mysql.yaml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> my-temporal</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --namespace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> temporal</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --create-namespace=true</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  --kube-context</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> ***</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> elasticsearch.enabled=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.default.sql.user=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">***</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.default.sql.password="***"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.visibility.sql.user=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">***</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.visibility.sql.password="***"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.default.sql.host=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">***</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> server.config.persistence.visibility.sql.host=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">***</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 更新版本执行 helm upgrade 同理</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>安装如果遇到helm dependency的问题，可以注释掉Prometheus、Grafana、ES等没有用到的依赖Chart。</p>
<p>注意：如果连接AWS Aurora数据库，需要在values.mysql.yaml下面需要加上 connectAttributes：</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  config</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    persistence</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        sql</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          connectAttributes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">            tx_isolation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'READ-COMMITTED'</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>第一次Install后，admintools这个Pod会正常运行，其他Pod会找不到数据库表失败，这时可以进去admintools的Pod shell，执行命令更新DB Schema，Schema的源文件在这里：<a href="https://github.com/temporalio/temporal/tree/master/schema/mysql/v57" target="_blank" rel="noreferrer">https://github.com/temporalio/temporal/tree/master/schema/mysql/v57</a>
。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SQL_PLUGIN</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mysql</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SQL_HOST</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mysql_host</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SQL_PORT</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3306</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SQL_USER</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mysql_user</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SQL_PASSWORD</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mysql_password</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/temporal/schema/mysql/v57/temporal</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">temporal-sql-tool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --connect-attributes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "tx_isolation=READ-COMMITTED"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --ep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> mysql-endpoint</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> ***</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --password</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "***"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --db</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> temporal</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> setup-schema</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./schema.sql</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/temporal/schema/mysql/v57/visibility</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">temporal-sql-tool</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --connect-attributes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "tx_isolation=READ-COMMITTED"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --ep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> mysql-endpoint</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> ***</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --password</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "***"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --db</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> temporal_visibility</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> setup-schema</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./schema.sql</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>等其他Pod自动重启或手动删除后，所有Temporal组件都会正常运行，可以Forward一个temporal-web的8080端口，检查Temporal服务是否运行正常。</p>
<h4 id="写代码" tabindex="-1">写代码 <a class="header-anchor" href="#写代码" aria-label="Permalink to &quot;写代码&quot;">&ZeroWidthSpace;</a></h4>
<p>搭建完Temporal服务后，我们就可以开始写第一个Workflow代码了。下面我们以Java为例，详细讲解一下完整的Workflow是如何开发的，其他的编程语言道理是完全一样的。</p>
<p>在开始之前，需要给项目添加一下SDK依赖，以Gradle为例。</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 最新版本参考：https://github.com/temporalio/sdk-java/releases</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'io.temporal:temporal-sdk:1.17.0'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p><strong>第1步，设计工作流本身的关键行为和输入输出（参数-返回值），即定义Workflow的执行函数、查询函数、信号函数</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">WorkflowInterface</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">WorkflowMethod</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    String </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">execute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Object </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">QueryMethod</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    List&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getSomeStatus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SignalMethod</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> manualComplete</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p><strong>第2步，设计工作流涉及的所有子活动的名称和输入输出数据，即定义Activity的执行函数，每个Workflow中的子活动的输入输出</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">ActivityInterface</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyActivities</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    String </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">doSometing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Object </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p><strong>第3步，最关键的一步：设计工作流的状态机逻辑，即在代码中编排Activity</strong>。</p>
<p>注意这里是不用关心Activity的具体实现实例的，因为Temporal需要感知到调用逻辑流，不能直接new Activity的实现类，需要建Activity的Stub对象进行编排，真正的实现类的实例后面会丢给Temporal Client管理，这里也无需实例化真正的Activity类。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyWorkflowImpl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> implements</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> final</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> MyActivities activities;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> boolean</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> someSignal </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyWorkflowImpl</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        activities </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Workflow.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newActivityStub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MyActivities.class, ActivityOptions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setStartToCloseTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Duration.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ofHours</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">execute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Object </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 这是一个真正的“分布式Sleep”</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Workflow.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Duration.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ofSeconds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">25</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        activities.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">doSomething</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 等待信号，用来实现cancel，或者多个独立工作流之间的信号协同</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Workflow.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.someSignal);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 异步执行，通常用于批量并行Activity，多个Promise可以Promise.all/anyOf</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Promise&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> promise </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            log.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">info</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"do something async"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Cancelled"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        })</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 批量Async之后需要对Promise进行同步，防止工作流提前结束</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		promise.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // return即WorkflowExecution结束，return值也会被支持化，作为这次Execution的Output</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 工作流可以是无限的，for / while / 递归 都是支持的</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Workflow Done!"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> manualComplete</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 调用 SignalMethod 后，Temporal会evaluate所有await的表达式</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.someSignal </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div><p><strong>第4步，实现真正干业务的Activity函数，可以是任意多个类和函数</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Slf4j</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Service</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyActivitiesImpl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> implements</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyActivities</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">doSomething</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Object </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            // 这里可以干任意业务逻辑，但不要在这里越俎代庖，调用Workflow的函数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            log.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">info</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Activity: {}, {}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, Activity.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getExecutionContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getRunId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(), Activity.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getExecutionContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getActivityId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (Exception </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            // 在Activity的实现里面包装一下Exception，对记录异常更友好</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            // 异常的Activity会自动Retry，Retry策略可以在初始化ActivityOptions的Builder里面配置</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            throw</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Activity.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">wrap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(e);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 执行时间太长的Activity可以阶段性发送heartbeat保活</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Activity.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getExecutionContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">heartbeat</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"I'm still running"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // Activity函数的所有参数、返回值也会被自动持久化，Workflow状态机产生状态转移和记录</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Activity Done !"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><p><strong>第5步，准备干活：初始化Temporal Client，连接本地或远程的Temporal的Front服务，注册代码定义的WorkflowType、Activity的实现类</strong></p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Slf4j</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Component</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">RequiredArgsConstructor</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> WorkflowManager</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 这里获取真正的Activity实现实例，在registerActivitiesImplementations传递给Temporal Client用来反射调用</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Resource</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> DeployActivities myActivities;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Getter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowClient client;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">PostConstruct</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> initWorkflowFactory</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 如果是本地测试，不想依赖真正的服务端，可以用newLocalServiceStubs()</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        WorkflowServiceStubs service </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowServiceStubs.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newConnectedServiceStubs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(WorkflowServiceStubsOptions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setTarget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"127.0.0.1:7233"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">validateAndBuildWithDefaults</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(), Duration.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ofSeconds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowClient.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(service);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        WorkerFactory factory </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkerFactory.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(client);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Worker worker </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> factory.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newWorker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MyTaskQueue"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 这个地方注册的每个类都是一个Workflow的定义，每一种是一个WorkflowType</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        worker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">registerWorkflowImplementationTypes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MyWorkflow.class);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        worker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">registerActivitiesImplementations</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(deployActivities);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        factory.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 一个简单的函数，封装通用的创建WorkflowStub的逻辑</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> Stub&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">startWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Class&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">workflowType</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, Object... </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">params</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        String workflowId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> UUID.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">randomUUID</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        WorkflowOptions workflowOptions </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowOptions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setWorkflowId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(workflowId).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setTaskQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MyTaskQueue"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        T workflowStub </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.client.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newWorkflowStub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(workflowType, workflowOptions);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Stub&#x3C;>(workflowId, workflowStub);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Data</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">AllArgsConstructor</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Stub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String workflowId;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> T stub;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br></div></div><p><strong>第6步，真的开始干活了：调用WorkflowStub，开始一次WorkflowExecution</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Service</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">RequiredArgsConstructor</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> WorkflowExecutor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Resource</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowManager workflowManager;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">startMyWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Object </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        String workflowId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> UUID.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">randomUUID</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        WorkflowOptions workflowOptions </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> WorkflowOptions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setWorkflowId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(workflowId).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setTaskQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MyTaskQueue"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        WorkflowManager.Stub&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">MyWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> myWorkflowStub </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> workflowManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">startWorkflow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MyWorkflow.class, param);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        myWorkflowStub.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getStub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">execute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Hello"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> workflowId;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><p><strong>第7步，写测试用例，或者加断点Debug代码</strong>。</p>
<p>单元测试和调试步骤可以参考这个文档：
<a href="https://docs.temporal.io/java/testing-and-debugging" target="_blank" rel="noreferrer">https://docs.temporal.io/java/testing-and-debugging</a></p>
<p>如果要加断点，需要注意环境变量加上 <strong>TEMPORAL_DEBUG=true</strong>， 否则会报 PotentialDeadlockException。</p>
<p>运行起来之后，就可以在Web UI看到这样的Workflow数据了：</p>
<p><img src="https://filecdn.code2life.top/temporal_running.png" alt=""></p>
<h2 id="实践与思考" tabindex="-1">实践与思考 <a class="header-anchor" href="#实践与思考" aria-label="Permalink to &quot;实践与思考&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="实践历程" tabindex="-1">实践历程 <a class="header-anchor" href="#实践历程" aria-label="Permalink to &quot;实践历程&quot;">&ZeroWidthSpace;</a></h4>
<p>2022年，我们基于Temporal开发了DevOps工作流引擎，覆盖公司内部多种类型的CI/CD/CV操作和系统集成，已经在公司产线广泛使用。</p>
<p>前端用蚂蚁开源的<a href="https://github.com/antvis/X6" target="_blank" rel="noreferrer">x6</a>对DSL和执行过程做了业务层的可视化，后端是用Temporal SDK处理编排逻辑，核心代码不到1000行，绝大多数后端代码只需聚焦在业务开发上，而不是分布式系统的各种复杂问题处理。调试过程甚至不需要打断点，只要在Dashboard查看Event History就有完整的、持久化的“分布式调用栈”，开发调试的体验完美。</p>
<p>当时选型DevOps工作流的底层引擎时，一开始没有发现Temporal这个大杀器，走了一些弯路。2022年国庆期间，我重新评估了系统的架构选型，综合18个维度来看，Temporal无疑是开发复杂工作流业务的不二选择。最后用Temporal把代码推倒重来，花了一整月的时间重构，目前来看结果还是不错的，下面的表格是当时的部分评估结果。</p>
<table tabindex="0">
<thead>
<tr>
<th></th>
<th>Jira Workflow</th>
<th>Temporal</th>
<th>Argo Workflow</th>
<th>Prefect</th>
<th>ConcourseCI</th>
<th>Petri Nets</th>
</tr>
</thead>
<tbody>
<tr>
<td>Allow Cyclic</td>
<td>Yes</td>
<td>Yes</td>
<td>DAG only</td>
<td>DAG only</td>
<td>DAG only</td>
<td>Yes</td>
</tr>
<tr>
<td>Programing Language</td>
<td>Java - SaaS</td>
<td>Golang</td>
<td>Golang</td>
<td>Python</td>
<td>Golang</td>
<td>Paper(Scala / Ruby implementations)</td>
</tr>
<tr>
<td>Workflow as Code</td>
<td>No</td>
<td>Yes (in Go/Java/TS/…)</td>
<td>No</td>
<td>Yes (in python)</td>
<td>No</td>
<td>N/A</td>
</tr>
<tr>
<td>Workflow as YAML/DSL</td>
<td>No</td>
<td>No (but easy to develop)</td>
<td>Yes</td>
<td>No</td>
<td>Yes</td>
<td>N/A</td>
</tr>
<tr>
<td>Support RESTful API</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>N/A</td>
</tr>
<tr>
<td>Task Processing Approach</td>
<td>N/A SaaS</td>
<td>Lightweight Reentrant Process</td>
<td>Container</td>
<td>Python backend</td>
<td>Container</td>
<td>N/A</td>
</tr>
<tr>
<td>Extensibility</td>
<td>Medium(need automation)</td>
<td>High</td>
<td>Medium</td>
<td>Medium</td>
<td>Medium</td>
<td>High</td>
</tr>
<tr>
<td>Popularity</td>
<td>High</td>
<td>High</td>
<td>High</td>
<td>High</td>
<td>Medium</td>
<td>Low</td>
</tr>
<tr>
<td>Integration Difficulty</td>
<td>Easy</td>
<td>Easy</td>
<td>Easy</td>
<td>Medium</td>
<td>Easy</td>
<td>Hard</td>
</tr>
<tr>
<td>Dynamic Workflow Creation</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Support Cron / Timer</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>N/A</td>
</tr>
<tr>
<td>Support Generic Request</td>
<td>Yes with automation</td>
<td>Yes with stub code</td>
<td>Yes with image and command</td>
<td>Yes with python code</td>
<td>Yes with image and command</td>
<td>N/A</td>
</tr>
<tr>
<td>Web UI</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes (simple)</td>
<td>No</td>
</tr>
<tr>
<td>Storage</td>
<td>Cloud</td>
<td>Cassandra/MySQL/Postgres</td>
<td>Kubernetes CR</td>
<td>SQLite/Postgres</td>
<td>Postgres</td>
<td>N/A</td>
</tr>
<tr>
<td>Client Integration Security</td>
<td>SaaS + API Token / OAuth</td>
<td>OnPrem + gRPC with mTLS</td>
<td>On-Prem + Kubernetes RBAC</td>
<td>On-Prem + API Token</td>
<td>On-Prem + mTLS</td>
<td>N/A</td>
</tr>
<tr>
<td>Scalability</td>
<td>High</td>
<td>High</td>
<td>Medium</td>
<td>High</td>
<td>Medium</td>
<td>N/A</td>
</tr>
<tr>
<td>Performance</td>
<td>Low (HTTP latency)</td>
<td>High</td>
<td>Low (start container)</td>
<td>High</td>
<td>Low (start container)</td>
<td>N/A</td>
</tr>
<tr>
<td>License</td>
<td>Enterprise Licensed</td>
<td>MIT</td>
<td>Apache 2.0</td>
<td>Apache 2.0</td>
<td>Apache 2.0</td>
<td>N/A (Ruby FlowCore - MIT)</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h4 id="使用关键注意点" tabindex="-1">使用关键注意点 <a class="header-anchor" href="#使用关键注意点" aria-label="Permalink to &quot;使用关键注意点&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>事件溯源模式的本质，要求<strong>工作流代码必须是确定性的</strong>（使用Workflow库提供的<strong>async</strong>函数，<strong>current time</strong>函数），一切非确定性的行为需要显式记录副作用，比如随机数；</li>
<li>不要在Activity代码里面调用Workflow，编排逻辑要全部放到Workflow的实现里；</li>
<li>由于Workflow的执行需要被跟踪状态和事件，Stub是每次调用动态创建的，因此，不要用IoC容器托管或者尝试实例化Workflow，但可以用IoC容器托管Activity的实现；</li>
<li>Workflow的参数和返回值会被记录，因此<strong>参数的类型不要出现自引用或嵌套递归</strong>，即用到WorkflowInfo之类的对象，否则会导致类似Direct self-reference leading to cycle的错误；</li>
<li>写Workflow代码的时候，<strong>思维模式需要切换到上帝视角，不需要关心在哪里运行</strong>。代码中的第一行可能跑在第一个Worker上，第二行可能跑在另一个Worker，第三行是在100天后等来了第四个另一个编程语言客户端的Worker发送的控制信令。</li>
</ul>
<h4 id="亮点和局限性" tabindex="-1">亮点和局限性 <a class="header-anchor" href="#亮点和局限性" aria-label="Permalink to &quot;亮点和局限性&quot;">&ZeroWidthSpace;</a></h4>
<p>在学习和实践过程中，有一些Temporal的亮点和局限性也顺便总结一下，个人感觉用的非常舒服的地方有这些：</p>
<ol>
<li>客户端提供了完善的多语言SDK和样例、单元测试：Go/Java/TS/Python/PHP/C#/Ruby，工作流的编程方式非常友好；</li>
<li>背后是从Uber出来创业的商业公司，高性能、稳定性好，可大规模用于产线；</li>
<li>自带WebUI对Workflow和Activity进行查询很方便；</li>
<li>对应用层的灵活性非常高，应用场景广泛；</li>
<li>在<strong>中心化状态机+事件溯源模式</strong>的模式下开发运行的代码非常健壮，Bug率低；</li>
<li>对于长时间跨度的业务处理很方便，无需引入一套分布式CronJob/Timer方案，一行代码可以实现分布式Timer/CronJob。</li>
</ol>
<p>体感比较明显的局限性不多，我能想到的有下面几个：</p>
<ol>
<li>偏底层，没有提供Workflow as DSL/Yaml，需要自己实现DSL和对应的可视化UI；</li>
<li>SDK的多语言和多框架的支持还没有特别完善，比如官方对SpringBoot自动配置的支持在开发中，更多编程语言的SDK还在陆续发布；</li>
<li>Temporal的Cron Job目前支持到分钟级别，不能设置秒级的Cron Job。</li>
</ol>
<h4 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h4>
<p>在学习Temporal的过程中，越深入越感觉到其设计之精妙，Temporal本质上是把Golang单机的CSP协程模型，扩展到了分布式系统，实现了Fault-tolerant分布式CSP。完善的持久化机制和跨时间和网络分区的容灾能力，尤其适合做复杂的业务流程、长时间跨度的业务，甚至是业务工作流引擎。</p>
<p>最后用几句不说人话的方式，总结一下Temporal的基本原理和设计思想：</p>
<p><strong>Temporal服务端</strong>本质上是<strong>通过信令和队列调度实现的中心化工作流引擎</strong>、及<strong>事件溯源模式实现的持久化长活事务和分布式的可重入进程</strong>。
<strong>Temporal客户端</strong>通过<strong>代理模式对执行逻辑进行切面控制，将单机函数输入输出，通过gRPC转为分布式的中心化调度和状态存取，进而实现编码阶段集中化、执行阶段分布式化</strong>。</p>
<p><strong>可重入进程+事件溯源思想的厉害之处在于，解决了分布式持久化状态机问题，<strong>同时用事件溯源保障了状态机的</strong>重入一致性</strong>。
<strong>去中心化的Worker保持了极致弹性能力、中心化状态机实现了跨应用的全局响应式编程，<strong>保障了全局的</strong>逻辑内聚性</strong>。</p>
<p>Temporal的3个关键特性，可中断、可恢复、响应式，正好是3个”R“：Resumable, Recoverable, Reactive。</p>
<blockquote>
<p>A Temporal Workflow Execution is a Reentrant Process. A Reentrant Process is resumable, recoverable, and reactive.
Resumable: Ability of a process to continue execution after execution was suspended on an awaitable.
Recoverable: Ability of a process to continue execution after execution was suspended on a failure.
Reactive: Ability of a process to react to external events</p>
</blockquote>
<p>我惊叹于其设计精妙时，也好奇是谁创造了这个项目呢？原来Temporal的核心创始人之一Maxim Fateev，有超过15年消息队列和工作流平台的积累，领导了AWS SQS, Azure Service Bus, Azure Durable Functions, Uber Cherami、Cadence项目。</p>
<p>Maxim Fateev和Samar Abbas<a href="https://temporal.io/about" target="_blank" rel="noreferrer">2019年出来创业</a>，在2022年初Temporal已经是估值<a href="https://www.geekwire.com/2022/temporal-is-a-unicorn-developer-productivity-startup-lands-120m-at-1-5b-valuation/" target="_blank" rel="noreferrer"><strong>15亿美元</strong></a>)的独角兽企业。Temporal的商业模式果然也是云服务，走Cloud的SaaS模式。基于一个纯开源的工作流引擎的公司，能给到这么高的估值，足以说明背后硅谷投资人的技术视野和基金实力。</p>
<h4 id="学习参考" tabindex="-1">学习参考 <a class="header-anchor" href="#学习参考" aria-label="Permalink to &quot;学习参考&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>源码仓库：<a href="https://github.com/temporalio/temporal" target="_blank" rel="noreferrer">https://github.com/temporalio/temporal</a></li>
<li>官网文档：<a href="https://docs.temporal.io/" target="_blank" rel="noreferrer">https://docs.temporal.io/</a></li>
<li>创始人Maxim讲解的Temporal详细原理：<a href="https://www.youtube.com/watch?v=t524U9CixZ0&amp;ab_channel=Temporal" target="_blank" rel="noreferrer">https://www.youtube.com/watch?v=t524U9CixZ0&amp;ab_channel=Temporal</a></li>
<li>Temporal分布式集群组件和原理：<a href="https://docs.temporal.io/clusters" target="_blank" rel="noreferrer">https://docs.temporal.io/clusters</a></li>
<li>7分钟快速入门案例：<a href="https://www.youtube.com/watch?v=2HjnQlnA5eY&amp;ab_channel=Temporal" target="_blank" rel="noreferrer">https://www.youtube.com/watch?v=2HjnQlnA5eY&amp;ab_channel=Temporal</a></li>
<li>3种使用场景详细介绍：<a href="https://www.youtube.com/watch?v=eMf1fk9RmhY&amp;ab_channel=Temporal" target="_blank" rel="noreferrer">https://www.youtube.com/watch?v=eMf1fk9RmhY&amp;ab_channel=Temporal</a></li>
</ul>
<p><strong>支持的主流编程语言示例</strong></p>
<ul>
<li>TypeScript SDK文档: <a href="https://docs.temporal.io/typescript/introduction/" target="_blank" rel="noreferrer">https://docs.temporal.io/typescript/introduction/</a></li>
<li>TypeScript示例代码：<a href="https://github.com/temporalio/samples-typescript/" target="_blank" rel="noreferrer">https://github.com/temporalio/samples-typescript/</a></li>
<li>Java SDK文档：<a href="https://docs.temporal.io/java/" target="_blank" rel="noreferrer">https://docs.temporal.io/java/</a></li>
<li>Java示例代码：<a href="https://github.com/temporalio/samples-java" target="_blank" rel="noreferrer">https://github.com/temporalio/samples-java</a></li>
<li>Golang SDK文档: <a href="https://docs.temporal.io/go/" target="_blank" rel="noreferrer">https://docs.temporal.io/go/</a></li>
<li>Golang示例代码：<a href="https://github.com/temporalio/samples-go" target="_blank" rel="noreferrer">https://github.com/temporalio/samples-go</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[关于权责利对等原则的思考]]></title>
            <link>https://code2life.top/blog/0063-p-r-i</link>
            <guid>https://code2life.top/blog/0063-p-r-i</guid>
            <pubDate>Tue, 27 Sep 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="关于权责利对等原则的思考" tabindex="-1">关于权责利对等原则的思考 <a class="header-anchor" href="#关于权责利对等原则的思考" aria-label="Permalink to &quot;关于权责利对等原则的思考&quot;">&ZeroWidthSpace;</a></h1>
<ul>
<li><a href="#引言">引言</a></li>
<li><a href="#权责利不对等会怎么样">权责利不对等会怎么样？</a>
<ul>
<li><a href="#000-无权无责无利则怠">000. 无权无责无利则怠</a></li>
<li><a href="#010-无权有责无利则阻">010. 无权有责无利则阻</a></li>
<li><a href="#110-有权有责无利则苛">110. 有权有责无利则苛</a></li>
<li><a href="#011-无权有责有利则谄">011. 无权有责有利则谄</a></li>
<li><a href="#001-无权无责有利则蚀">001. 无权无责有利则蚀</a></li>
<li><a href="#100-有权无责无利则腐">100. 有权无责无利则腐</a></li>
<li><a href="#101-有权无责有利则莽">101. 有权无责有利则莽</a></li>
<li><a href="#111-有权有责有利则效">111. 有权有责有利则效</a></li>
</ul>
</li>
<li><a href="#总结">总结</a></li>
</ul>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>记得在MBA人力资源课上，学过下面这张统计图，对企业资源进行<strong>重新组合</strong>的时候，是企业所有发展阶段中最具挑战的。2022年，我投身于一个特殊项目，做内部基础设施的重构整合，90%以上的时间都花在了与形形色色团队的沟通上，在过程中也对学的理论有了真实体感，比如定岗需遵循的<strong>权责利对等原则</strong>，而本文是我对“权责利对等原则”的一些思考。</p>
<p><img src="http://filecdn.code2life.top/stars-mba.png" alt=""></p>
<p>（图片来自USTC MBA课程的课件）</p>
<h2 id="权责利不对等会怎么样" tabindex="-1">权责利不对等会怎么样？ <a class="header-anchor" href="#权责利不对等会怎么样" aria-label="Permalink to &quot;权责利不对等会怎么样？&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>权责利对等</strong>是构建高绩效团队的关键原则。能力要求相似的团队，往往表现出巨大的绩效差异，通常会被归因于项目问题、管理过程问题，高层管理者却忽视了<strong>岗位本身和制度设计</strong>是否有问题。尤其是在某些大企业、官僚机构中，可能存在大量权责利不对等的问题岗位。</p>
<p>如果我们把权、责、利的三角各取0或1，组合出来8种情况，依次分析、推演这8种情况，可以解释一些企业中普遍存在的现象。</p>
<h4 id="_000-无权无责无利则怠" tabindex="-1">000. 无权无责无利则怠 <a class="header-anchor" href="#_000-无权无责无利则怠" aria-label="Permalink to &quot;000. 无权无责无利则怠&quot;">&ZeroWidthSpace;</a></h4>
<p>没有资源做事，无需承担任何责任，做了事也没有收益，企业中一般不会有这种岗位，也没有人会愿意加入这类团队中。现实中这类岗位可能是由于并购或内部斗争，上层被架空后的“弃子”，可能是将要裁撤掉的岗位。</p>
<h4 id="_010-无权有责无利则阻" tabindex="-1">010. 无权有责无利则阻 <a class="header-anchor" href="#_010-无权有责无利则阻" aria-label="Permalink to &quot;010. 无权有责无利则阻&quot;">&ZeroWidthSpace;</a></h4>
<p>没有决定权，却要承担责任，承担了责任也没有额外收益。这个看上去很不合理的情形，却真实存在于某些组织中。</p>
<p>设想一下，如果你是某审核部门的“临时工”，某天看到了巨大的风险，但你知道，真正的决策在上层，自己并没有实际否决权，一旦出事自己可能要背锅。这时，你会怎么做呢？一个理性人，会想方设法避免自己“背锅”。典型的做法是：说服那个有真正决策权的人，设法让流程变的冗长复杂，增加审核的人手，转移自己的责任。</p>
<p>哪天东窗事发，总会找到一个“临时工”来背锅的。根源是这种形同虚设的审核岗位，阻碍了效率的提高，风险却没有降低一丁点。</p>
<p>如果从多方博弈的角度看，这种情形是<strong>非纳什均衡</strong>态，在这类岗位工作的“理性人”，还可能采取行动转换为下面3种均衡态之一：</p>
<ol>
<li>如果是三观正常，有一些道德追求的人，会想办法<strong>拿回决策权</strong>，避免风险，走向第二种有权有责无利（110）模式；</li>
<li>如果道德底线低一些，可能会拿着自己了解的信息，设法牟利，走向第四种无权有责有利（011）模式；</li>
<li>如果佛系一些，选择甩锅、躺平、辞职，走向第一种无权无责无利（000）模式。</li>
</ol>
<h4 id="_110-有权有责无利则苛" tabindex="-1">110. 有权有责无利则苛 <a class="header-anchor" href="#_110-有权有责无利则苛" aria-label="Permalink to &quot;110. 有权有责无利则苛&quot;">&ZeroWidthSpace;</a></h4>
<p>有决策权，需要承担后果，但却不能获得相匹配的收益。企业中广泛存在这类岗位，有的是<strong>制度设计</strong>造成的，有的是<strong>蛋糕不够分</strong>造成的。</p>
<p>2022年的典型案例是：疫情防控，我不会评论任何政策的好坏，而是思考现象的本质是什么。为什么顶层制定的非常合理的防疫政策，在基层执行过程中变得更<strong>严苛</strong>了呢？地方政府有决策权、也同时承担着“不仅要算经济账”这个超越经济利益的巨大责任。在这样的岗位和制度下，任何一个理性人，都一样会做出保守、严苛的防疫政策。</p>
<p>蛋糕不够分造成的有权有责无利也很常见，有外因或者内因。比如，目前全球下行的经济形势下，大多在降低薪水、收缩福利。“多做多错，少做少错”，“吃力不讨好” 这类负面情绪明显增加了。“利”的不足，通常做法就是“求稳”，让事情做的更慢，最大程度避免风险、让“责”与“利”更平衡一些。而内因的场景情形，是大多是中高层的利己决策、盲目扩张，人为造成的“蛋糕不够分”。“财聚人散，财散人聚”的道理很简单，但实践中运用的恰到好处的决策者太少。</p>
<p>当然，“有权有责无利”在也不完全是均衡态，对于优秀人才来说，跳槽、择良木而栖是一个办法；对于道德容易滑坡的人来说，把“权”转换成“利”，通过贪污平衡责任和利益也是一个办法。明朝有严厉的惩戒贪官的制度，为何还出现了杀不完的贪官呢？一个关键原因，是背后的制度问题。</p>
<h4 id="_011-无权有责有利则谄" tabindex="-1">011. 无权有责有利则谄 <a class="header-anchor" href="#_011-无权有责有利则谄" aria-label="Permalink to &quot;011. 无权有责有利则谄&quot;">&ZeroWidthSpace;</a></h4>
<p>没有决策权，做事有足够的收益和相应的责任。这类场景也广泛存在于中国企业。举个例子，小A是一名销售，现在想开拓一片新市场，但决策权在总部高层领导手上，正常渠道又没办法申请到足够的资源干这件“大事”。小A只好想尽办法，比如在饭局喝掉自杀剂量的乙醇、设法讨好甚至贿赂决策者来达成目标。</p>
<p>出现这类情况原因基本都是<strong>过度集权，赋能不足</strong>。错误的制度设计，该决策的人没有决策权，不该决策的人拿了过大的决策权，诱导了道德滑坡，导致一端以利换权，另一端以权换利，监督、重罚治标，但不治本。国内多数企业的文化，恰好给<strong>过度集权</strong>提供了坚实的土壤。</p>
<h4 id="_001-无权无责有利则蚀" tabindex="-1">001. 无权无责有利则蚀 <a class="header-anchor" href="#_001-无权无责有利则蚀" aria-label="Permalink to &quot;001. 无权无责有利则蚀&quot;">&ZeroWidthSpace;</a></h4>
<p>这种情况是上一种情况的变种，但拿到决策者的批准后，连责任都不用承担，就可以躺赚了。没有足以匹配的责任，做起事来会更加不计后果。“假口罩”、“假核酸”、以及各类食品安全问题的背后，是对行政机构的侵蚀，也是问责和惩戒制度的不完善。</p>
<h4 id="_100-有权无责无利则腐" tabindex="-1">100. 有权无责无利则腐 <a class="header-anchor" href="#_100-有权无责无利则腐" aria-label="Permalink to &quot;100. 有权无责无利则腐&quot;">&ZeroWidthSpace;</a></h4>
<p>有决策权，却不承担权力滥用的责任，同时也没有足够的利益，这种情况下的岗位以及背后的人，很容易沦为彻底的权力寻租，是比贪更严重的“以权换利”，和上一类情况正好形成一对道德滑坡组合，一方侵蚀，另一方腐败。可见，责任不到位的岗位和制度设计，对组织的危害极大。</p>
<h4 id="_101-有权无责有利则莽" tabindex="-1">101. 有权无责有利则莽 <a class="header-anchor" href="#_101-有权无责有利则莽" aria-label="Permalink to &quot;101. 有权无责有利则莽&quot;">&ZeroWidthSpace;</a></h4>
<p>有权力做决策，也有匹配的收益，但不承担错误决策的后果。一个典型案例是大企业过度招聘问题，很多基层业务部门领导可以向人力资源部门申请岗位，为了自己和自己部门的利益，他们会倾向于过度申请人力资源，反正不用承担用人成本。最终后果是过度招人，显得部门很大发展很好，然而事实是，这些人被安排到了鸡肋的岗位，让高层裁也不是、留也不是。没有足够的责任，失去了谨慎的心态，也就变得更加鲁莽。</p>
<h4 id="_111-有权有责有利则效" tabindex="-1">111. 有权有责有利则效 <a class="header-anchor" href="#_111-有权有责有利则效" aria-label="Permalink to &quot;111. 有权有责有利则效&quot;">&ZeroWidthSpace;</a></h4>
<p>权责利对等是最高效、安全的岗位定义和运作方式。稻盛和夫推崇的“阿米巴”模式，本质上就是这种原子化的、权责利对等的运作模式。每一个原子组织，都有完备的业务和自主决策的团队，对自己的决策损益负责，这样能够极大激发每一个原子组织的经营者意识。唤起了经营者意识，才有可能更进一步，唤起主人翁意识。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>这8类情况中，除了无权无责无利（000）和有权有责有利（111），其他6种情况在企业和机构中或多或少都会出现。当权责利不对等时，由于人性使然，人们总会想办法增加权、利，减少责，形成动态平衡。</p>
<p>分成这8类只是为了分析行为演化的方便，在现实企业和官僚机构中，很多现象是灰度的，不同组也会形成更复杂的局面。洞察这些现象的本质，可以让我们少一些不必要的愤世嫉俗，也可以给管理者优化岗位定义、制度设计提供参考。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[边际革命-企业模拟与GMC参赛感悟]]></title>
            <link>https://code2life.top/blog/0063-gmc</link>
            <guid>https://code2life.top/blog/0063-gmc</guid>
            <pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="边际革命-企业模拟与gmc参赛感悟" tabindex="-1">边际革命-企业模拟与GMC参赛感悟 <a class="header-anchor" href="#边际革命-企业模拟与gmc参赛感悟" aria-label="Permalink to &quot;边际革命-企业模拟与GMC参赛感悟&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>天之道，损有余而补不足，人之道，损不足而奉有余。——《道德经》</p>
</blockquote>
<h2 id="均衡之道" tabindex="-1">均衡之道 <a class="header-anchor" href="#均衡之道" aria-label="Permalink to &quot;均衡之道&quot;">&ZeroWidthSpace;</a></h2>
<p>最近在学习MBA的《数据模型与决策》和《管理经济学》课程，讲到多元函数的极值问题，老师提到拉格朗日乘子法。我们就从一个简单的例子出发，聊聊什么是拉格朗日乘数，以此引出参加企业模拟对抗赛和全球企业管理挑战赛（GMC）后，对战略的理解和感悟。</p>
<p>以三元函数为例，设一个三元函数L=f(x, y, z), 其中 x,y,z 满足约束 φ(x,y,z)=m,则:</p>
<ol>
<li>L = f(x, y, z) + λφ(x, y, z) 其中λ称拉格朗日乘数；</li>
<li>求L分别对x,y,z,λ的偏导为0的方程组，求出驻点P(x,y,z)，一般即为极值点。</li>
</ol>
<p>当年学高数的时候从没有没想过，为什么要让偏导为0呢？最近在MBA的学习和实践中有了更深刻的理解。</p>
<p>我们把x, y, z 想象成我们可支配的资源，比如健康、时间、金钱。φ是三者互相制约的关系。在某个瞬间，我们恰好平衡了三者，不愿意再多耗哪怕一点健康去换取金钱，也不愿意再花哪怕一点时间来锻炼身体，刚刚好。也就是说在<strong>联立λ系数的约束条件</strong>后，L函数对4个变量偏导均为0，这时，F函数的<strong>3种资源有等价的“阴影价格</strong>”。</p>
<p>这种平衡是动态的，我们永远都在为“N元变量在约束条件下偏导为0”的目标努力着。</p>
<p>每个人都是平衡边际效用的高手，即使是那些熬夜、拼命加班的人。在他们的世界里，牺牲健康，换取对他们来说更重要的东西。那些东西对他们的边际效用太高了，而他们自认为健康的对他们自己的边际效用很低，因此只需牺牲很少量的其他资源保障最基本的健康，就可以达到他们心目中的平衡点。</p>
<h2 id="什么是战略" tabindex="-1">什么是战略 <a class="header-anchor" href="#什么是战略" aria-label="Permalink to &quot;什么是战略&quot;">&ZeroWidthSpace;</a></h2>
<p>从拉格朗日乘数的含义可以得出：<strong>一种资源分配到多处，当各处边际贡献率趋同时，总效用最大</strong>。比如一个产品投放到不同市场，当不同市场的边际贡献率，即(单价-变动成本) ÷ 单价，相同时，这个投放比例<strong>在此刻</strong>是最恰当的。这与老子所说的“天之道，损有余而补不足”异曲同工。</p>
<p><strong>然而，这样做真的是最优解吗</strong>？</p>
<p>不，世界是动态变化的。今日的均衡，明日就可能烟消云散。</p>
<p><strong>战是方向，略是舍弃</strong>。</p>
<p>战，是拓展那些当前边际贡献率低，未来可能极高的领域；</p>
<p>略，是舍弃那些当前边际贡献率还不错，但未来可能会降至负数的累赘。</p>
<p>此刻的资源倾斜，是为了押注在未来边际效用更高的资源配置方式。</p>
<p>“人之道，补不足而奉有余”。这既是人性贪婪的一面，也是人类智慧的一面。战略就是，你会坚信，现在的战略布局，哪怕不被理解，在未来某天回看当下的短期损失，一定会低于未来总收益的折现值。</p>
<p>如果战略判断对了，留在幸存者的世界，是天人合一之道；如果错了，那就消散在历史长河吧，但求无悔。</p>
<h2 id="战略就是要打破当下的均衡" tabindex="-1">战略就是要打破当下的均衡 <a class="header-anchor" href="#战略就是要打破当下的均衡" aria-label="Permalink to &quot;战略就是要打破当下的均衡&quot;">&ZeroWidthSpace;</a></h2>
<p>更深刻的理解战略是什么之后，我们再以未来视角去看一些企业行为、国家政策，会有不一样的体会。</p>
<p>比如最近这几年，不断有造手机的企业下场造芯片，造芯片巨大的投入，不仅失败的风险巨大，而且在很长时间内带来的收益都不可能覆盖研发成本，为什么还是要做呢？</p>
<p>卡脖子问题国家队在攻克，手机厂商专心做手机，牛刀杀牛，菜刀杀鸡，难道不是更“均衡”的做法么？杀鸡焉用牛刀？</p>
<p>但是，我们站到未来去看，就不难理解为什么手机厂商去造芯片了。采购芯片是当下最优解，而打破当下的最优均衡点，可能会带来什么呢？可能是知识产权、可能是手机使用体验的质变、可能是转型造其他硬件设备的技术积淀。造芯片的战略，是以财务利润为代价，押注向<strong>更高阶均衡点</strong>演化的机会。</p>
<p><strong>什么是自主创新？没有自主，就没有创新</strong>。</p>
<p><strong>向内演化 — involution，叫内卷；向外演化 — evolution，叫进化</strong>。不去用战略的眼光打破当下的均衡，在未来可能是过度竞争、甚至是一片死寂。</p>
<p>一个企业，即使在正确的战略下，顺利演化生长下去，最终还是要学会适应&quot;逻辑曲线&quot;的制约。“边际效用递减”、“负反馈调节”，这些来自不同领域的智慧，都在描述着同一个本质，自然之道。终究，有一个增长的极限，提前担起社会责任，不要再“损不足而奉有余”，或许是最好的归宿。</p>
<blockquote>
<p>天之道，损有余而补不足，人之道，损不足而奉有余。孰能有余以奉天下，唯有道者。</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[为什么你可能不需要Nacos？]]></title>
            <link>https://code2life.top/blog/0061-spring-boot-dynamic-config</link>
            <guid>https://code2life.top/blog/0061-spring-boot-dynamic-config</guid>
            <pubDate>Mon, 31 May 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="为什么你可能不需要nacos" tabindex="-1">为什么你可能不需要Nacos？ <a class="header-anchor" href="#为什么你可能不需要nacos" aria-label="Permalink to &quot;为什么你可能不需要Nacos？&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="配置中心会有多复杂" tabindex="-1">配置中心会有多复杂？ <a class="header-anchor" href="#配置中心会有多复杂" aria-label="Permalink to &quot;配置中心会有多复杂？&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>配置中心</strong>是微服务系统必不可少的组件之一，乍一看好像没多少技术含量，可是，真的是这样吗？</p>
<p>以Java Spring技术栈为例，主流的配置中心有阿里的Nacos、携程的Apollo、以及Spring Cloud Config Server。我们拆解一下其中共通的技术点：</p>
<p><strong>服务端</strong>：</p>
<ul>
<li>认证和权限控制：某个服务可以拿到哪些Key？人员的增删改查权限如何控制？</li>
<li>存储层的选型：文件系统，Git仓库，数据库？</li>
<li>安全性：传输加TLS，密钥需要落盘加密，本身用来加密密钥的密钥如何安全存储？</li>
<li>高可用、数据一致性：多实例部署，甚至跨区域同步，进而又带来分布式存储一致性问题，如何解决？</li>
<li>版本控制：修改记录需要保留，随时可能回退到历史版本，另外，灰度版本的配置隔离如何实现？</li>
</ul>
<p><strong>客户端</strong>：</p>
<ul>
<li>SDK如何兼容不同的技术栈？</li>
<li>大量客户端同时启动，如何做并发控制？同时还要尽可能减少额外的请求，对服务启动时间的负面影响？</li>
<li>如何实现与本地配置的优先级控制、合并、缓存、变化实时感知？</li>
</ul>
<p>...</p>
<p>看到这里，或许你不会觉得配置中心只是简单的KV存储了。主流的型如Alibaba Nacos，作为一个完善的配置和服务发现组件，已经解决了上述大部分问题。我也曾用Nacos，Nacos非常棒，不过我也逐渐发现了一些局限性：</p>
<ul>
<li>当你有数十个环境，每个环境有数百个配置的时候，基于图形界面的版本管理会力不从心；</li>
<li>Nacos服务的网络、Server、数据库，任何一层出问题，都可能影响大量产线服务；</li>
<li>额外的学习、使用、部署、维护成本，服务启动的额外性能开销；</li>
<li>发生过一次产线事故，后来分析是因为Spring Cloud Nacos在刷新配置的时候，可能导致Bean Refresh死锁；</li>
<li>不支持新版本的SpringBoot/SpringCloud，在SpringBoot 2.3.0.M1之后直接报错（这个问题存在很久了，我在Github提了一个Pull Request修复该问题，多日没有收到答复 <a href="https://github.com/nacos-group/nacos-spring-boot-project/pull/189%EF%BC%89" target="_blank" rel="noreferrer">https://github.com/nacos-group/nacos-spring-boot-project/pull/189）</a></li>
</ul>
<h4 id="挥下奥卡姆剃刀吧-或许你不需要如此复杂的方案" tabindex="-1">挥下奥卡姆剃刀吧，或许你不需要如此复杂的方案！ <a class="header-anchor" href="#挥下奥卡姆剃刀吧-或许你不需要如此复杂的方案" aria-label="Permalink to &quot;挥下奥卡姆剃刀吧，或许你不需要如此复杂的方案！&quot;">&ZeroWidthSpace;</a></h4>
<h2 id="一行代码实现动态配置" tabindex="-1">一行代码实现动态配置 <a class="header-anchor" href="#一行代码实现动态配置" aria-label="Permalink to &quot;一行代码实现动态配置&quot;">&ZeroWidthSpace;</a></h2>
<p>我萌生了一个朴素的想法：既然<strong>配置原本就是单纯的文件</strong>，那么<strong>文件变化时，重新加载对应的Spring Bean</strong>不就行了吗？</p>
<p>于是，我开发了一个<strong>Spring Boot的配置热重载库</strong>，已发布到Maven中心仓库，Github开源仓库地址：<a href="https://github.com/Code2Life/spring-boot-dynamic-config" target="_blank" rel="noreferrer">https://github.com/Code2Life/spring-boot-dynamic-config</a>。</p>
<p>话不多说，先看效果。</p>
<p><img src="//filecdn.code2life.top/springboot-config-demo.gif" alt=""></p>
<p>这个库的使用方式<strong>极其简单</strong>：只要在<strong>注有@Value/@ConfigurationProperties的类</strong>上，<strong>加上@DynamicConfig注解</strong>即可。</p>
<h2 id="实现原理" tabindex="-1">实现原理 <a class="header-anchor" href="#实现原理" aria-label="Permalink to &quot;实现原理&quot;">&ZeroWidthSpace;</a></h2>
<p>我读了一些Nacos、Spring Boot、Spring Cloud的相关源码后，发现实现热重载配置有<strong>两类</strong>方案：</p>
<ol>
<li>直接基于Spring/SpringBoot，通过自定义Bean的加载和PostProcessor机制开一个口子来实现；</li>
<li>二是基于Spring Cloud，本身就直接支持了动态配置。</li>
</ol>
<p>我<strong>不想依赖Spring Cloud的任何组件</strong>，选择了实现难度更大的第一类方案，更难是因为单纯的Spring Boot没有Spring Cloud Starter的父Context和<a href="https://gist.github.com/dsyer/a43fe5f74427b371519af68c5c4904c7" target="_blank" rel="noreferrer">@RefreshScope注解</a>，不能直接destroy原来的Bean，refresh一个新Bean出来，得“飞行中换引擎”。</p>
<h4 id="文件变化监听" tabindex="-1">文件变化监听 <a class="header-anchor" href="#文件变化监听" aria-label="Permalink to &quot;文件变化监听&quot;">&ZeroWidthSpace;</a></h4>
<p>第一步，从Environment Bean的PropertySources里，把文件配置的PropertySource给<strong>揪出来</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 需要先实现EnvironmentAware接口或自动装配StandardEnvironment</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">MutablePropertySources propertySources </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> environment.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getPropertySources</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (PropertySource&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> ps </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> propertySources) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    boolean</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isFilePropSource </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> isFromConfigFile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ps);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (isFilePropSource) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 找到配置文件的PropertySource</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>第二步，用<strong>Java NIO的Watch API</strong>监听配置目录。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">watchService </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FileSystems.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getDefault</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">newWatchService</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Paths.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(configLocation).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">register</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">WatchKey key;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ((key </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> watchService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">take</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (WatchEvent&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> event </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> key.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pollEvents</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        reloadChangedFile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(event)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    key.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>第三步，如果配置文件变了，把原来的<strong>PropertySource对象replace</strong>成重新创建出来的。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">propertySources.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(filePropertySourceName, newPropertySource);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 上述代码只是一些示意片段，完整的实现参考在 DynamicConfigPropertiesWatcher 类</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>至此，我们用<strong>数十行代码</strong>，已经实现了<strong>动态的Environment  Bean</strong>，用<strong>getProperty()获得的结果已经是动态</strong>的了。</p>
<p>到此就结束了吗？</p>
<p>Spring Boot开发者一般是在Bean中<strong>使用@Value、@ConfigurationProperties</strong>来<strong>注入</strong>配置内容的，因此，<strong>原始的配置值已经分散IoC容器里各个相关的Bean中</strong>，我们还需要更进一步，在发生变化的同时，把这些Spring Context<strong>碗里相关的豆豆再揪出来，偷梁换柱</strong>。</p>
<h4 id="修改spring-bean的属性" tabindex="-1">修改Spring Bean的属性 <a class="header-anchor" href="#修改spring-bean的属性" aria-label="Permalink to &quot;修改Spring Bean的属性&quot;">&ZeroWidthSpace;</a></h4>
<ol>
<li>BeanPostProcessor切入Spring Bean加载流程，<strong>筛选出</strong>需要热重载Bean，完整代码在<a href="https://github.com/Code2Life/spring-boot-dynamic-config/blob/main/src/main/java/top/code2life/config/DynamicConfigBeanPostProcessor.java" target="_blank" rel="noreferrer">DynamicConfigBeanPostProcessor</a>类。</li>
<li>接到配置变化通知时，开始移花接木，完整代码在<a href="https://github.com/Code2Life/spring-boot-dynamic-config/blob/main/src/main/java/top/code2life/config/ConfigurationChangedEventHandler.java" target="_blank" rel="noreferrer">ConfigurationChangedEventHandler</a>类。</li>
</ol>
<h4 id="遇到的问题" tabindex="-1">遇到的问题 <a class="header-anchor" href="#遇到的问题" aria-label="Permalink to &quot;遇到的问题&quot;">&ZeroWidthSpace;</a></h4>
<p>因为要处理许多异常情况、兼容性问题等等，遇到了这两个问题：</p>
<ul>
<li>Linux软链接/Windows快捷方式文件，无法用JDK的WatchService监听。于是加了一个兜底策略：轮询文件的上次修改时间；</li>
<li>@ConfigurationProperties类/嵌套类中如果有Map属性，Spring Boot的行为是做<strong>Merge</strong> Keys，而不是<strong>清空</strong>原有的Keys。我稍微改了一下，让最外层的Map变成清空策略；</li>
<li>Spring Boot 从2.0到现在2.5，有一些核心类发生了变化，PropertySource的命名也有变化，做了一些兼容，而Spring Boot 1.x的版本差别太大无法兼容了。</li>
</ul>
<h2 id="在生产环境使用spring-boot-dynamic-config" tabindex="-1">在生产环境使用Spring Boot Dynamic Config <a class="header-anchor" href="#在生产环境使用spring-boot-dynamic-config" aria-label="Permalink to &quot;在生产环境使用Spring Boot Dynamic Config&quot;">&ZeroWidthSpace;</a></h2>
<p>读到这里，或许你会质疑，这样做在<strong>本地开发</strong>没问题，但直接用<strong>文件</strong>的方式来管理开发/产线环境的配置，不是在开倒车吗？难道部署100个实例，要去100台机器上改配置文件？</p>
<p>当然不是。<strong>对配置文件的修改，一定，一定，一定要在Git仓库中</strong>，最好是独立于代码仓库之外的配置仓库。</p>
<h4 id="为什么要用git管理配置" tabindex="-1">为什么要用Git管理配置？ <a class="header-anchor" href="#为什么要用git管理配置" aria-label="Permalink to &quot;为什么要用Git管理配置？&quot;">&ZeroWidthSpace;</a></h4>
<p>我参与了<strong>数十个</strong>Spring Cloud服务在全球<strong>十几个数据中心</strong>的容器化部署和运维，深刻体会了配置管理中的痛点。我们从相对简单的SpringCloud Config，换到功能复杂的Nacos，都没有解决掉<strong>本质的问题</strong>：<strong>应用配置是DevOps的一环</strong>，本应该和其他环节一样，通过<strong>GitOps的持续交付流水线实现自动化，不是去登录任何一个系统去输入任何一行配置</strong>。</p>
<p>我们设想一个场景，你作为一名开发，现在想更新一行产线配置。</p>
<p><strong>Nacos的工作流是什么样的呢</strong>？</p>
<ul>
<li>写一个<strong>部署文档</strong>或者<strong>发邮件</strong>告诉运维同事，要做一个什么样的修改；</li>
<li>运维同事收到邮件，找你开会确认；</li>
<li>上线期间，运维同事登录Nacos，找到对应的Namespace和DataID，点击“编辑”<strong>按钮</strong>；</li>
<li>你告诉运维要改的在哪一行，运维同事帮你修改好，点击“发布”<strong>按钮</strong>；</li>
<li>测完预上线环境，告诉运维同事没问题可以发布到产线了，运维再去<strong>手工操作</strong>上述步骤。</li>
</ul>
<p><strong>Git的工作流是怎么样的呢</strong>？</p>
<ul>
<li>使用<strong>你喜欢的IDE</strong>打开你的应用配置仓库（这个仓库仅维护<strong>该应用服务的非敏感配置</strong>）；</li>
<li>你改完后提交一个合并请求给领导/运维，同事确认无误点击合并，<strong>自动触发Pipeline</strong>实现配置上线，是哪个分支哪个环境就<strong>立即</strong>生效。</li>
</ul>
<p>你<strong>不需要登录</strong>任何“配置管理系统”；你的运维同事不需要敲N下键盘、点N次鼠标；你不需要发邮件、写文档；甚至不需要和领导/运维同事发消息，<strong>整个过程就如丝般顺滑的在Git上完成了</strong>。</p>
<p>如果是开发环境，审批和走查流程灵活一些的话，配置Push到Git的开发分支，<strong>自动</strong>触发CI Pipeline，喝口水就生效了。</p>
<p>从这个场景可以看出，<strong>维护配置的职责交给Git，有很多好处</strong>。</p>
<ul>
<li>维护期间，可以<strong>利用Git管理系统的权限机制</strong>，实现对谁可以提交，如何Review，谁可以合并到产线等等<strong>细粒度控制</strong>；</li>
<li>Git本身具备完善的<strong>持续集成/持续交付（CI/CD）生态</strong>，通过触发CI/CD Pipeline，可以做到配置的<strong>秒级上线，跨区域同步上线</strong>；</li>
<li>Git的<strong>版本控制能力极强</strong>，把配置文件从GUI系统放回到Git，是配置变得复杂之后的<strong>必然选择</strong>；</li>
<li>可以用你喜欢的<strong>任何编辑器或者IDE</strong>，甚至是Git管理系统自带的Web IDE都可以，<strong>语法检查</strong>也有了；</li>
<li>非常灵活，可以使用<strong>模板引擎生成配置</strong>，甚至自己开发DSL，这些都可以集成到CI/CD Pipeline中。</li>
</ul>
<p>读到这里，或许你还有疑问：<strong>Git仓库里的配置内容，怎么就通过一个神奇的流水线</strong>，“变”到产线的那么多服务器的文件系统里面呢？还有在Git里面，肯定不能维护<strong>产线密钥</strong>，怎么办呢？</p>
<p><strong>答案是Kubernetes</strong>。</p>
<h4 id="kubernetes的魔法" tabindex="-1">Kubernetes的魔法 <a class="header-anchor" href="#kubernetes的魔法" aria-label="Permalink to &quot;Kubernetes的魔法&quot;">&ZeroWidthSpace;</a></h4>
<p>复杂的分布式系统，离不开基础平台的支持，Kubernetes就是这样一个<strong>业界标准级</strong>的云原生操作系统。</p>
<p>上一节说的神奇的流水线，<strong>并不是把文件拷贝到产线机器上</strong>，而仅仅是调用了<strong>一个HTTPS请求</strong>，来更新<strong>Kubernetes ConfigMap</strong>资源。</p>
<p>我们先回头看下一开始提的配置中心的技术难点，是怎么被<strong>Kubernetes ConfigMap/Secret</strong>功能<strong>完美解决</strong>的。</p>
<p><strong>服务端</strong>：</p>
<ul>
<li>认证和权限控制：Kubernetes天然的namespace隔离 + RBAC授权机制，读写权限可以<strong>控制到单个文件</strong>的粒度；</li>
<li>存储层的选型：职责分离 —— <strong>维护期间在Git，运行期间在Kubernetes背后的etcd</strong>，由CI/CD Pipeline自动同步Git到集群。<strong>既易维护，又保持了K-V存储服务的架构简单</strong>；</li>
<li>安全性：Kubernetes天然的TLS双向认证体系，传输是安全的。另外，Kubernetes Secret天然支持云服务商的<strong>KMS集成</strong>，更严格的权限控制，所有的<strong>密钥是单独管理</strong>、<strong>单独挂载</strong>到服务运行实例上的，<strong>压根不会出现在Git中</strong>；</li>
<li>高可用、数据一致性：背后的ectd集群使用<strong>Raft算法</strong>保障一致性。虽然etcd是<strong>CAP取CP</strong>，但即使整个K8S集群不可用，也<strong>不会影响任何已经运行的服务</strong>，因为ConfigMap/Secret早已在<strong>初始化Pod的容器阶段</strong>，就挂载成了<strong>普通的文件/环境变量</strong>；</li>
<li>版本控制：运行期etcd只保留个别历史版本，完善的版本管理职责，<strong>转移</strong>给了Git这样一个天然的版本控制系统。</li>
</ul>
<p><strong>客户端</strong>：文件/环境变量就是最原始的配置方式，应用层<strong>没有任何额外性能开销和学习使用成本</strong>，也天然兼容任何现有的技术栈，<strong>只需要在文件变化时在应用层做一次reload即可。对，Spring Boot Dynamic Config项目干的就是这个事</strong>。</p>
<p>注：每个K8S集群节点上运行的Kubelet，虽然会<strong>Watch资源的实时变化</strong>，但真正的更新是在<strong>1分钟的同步周期</strong>做的，也就是说配置修改在<strong>1分钟内会在所有运行实例中</strong>生效。这个机制是对Kubernetes API Server的<strong>请求削峰和保护</strong>，对于<strong>小型集群</strong>可以在Kubelet的启动参数减小“syncFrequency”的默认时间，加速配置生效。</p>
<p>下面是在Kubernetes中使用的Yaml示例片段。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 1. 前提： 在Jenkins/Argo CD/Github Actions中，</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#    创建Config Git Repo到Kubernetes ConfigMap的同步Pipeline</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 2. 在声明的K8S Deployment中，</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#    添加环境变量 SPRING_CONFIG_LOCATION</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#    指向Config Map mountPath，注意需要'/'结尾</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Deployment</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">apps/v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      volumes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">conf-path</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          configMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">            name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-app-config</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      containers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">app-container</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'&#x3C;your-image-name>'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          env</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">SPRING_CONFIG_LOCATION</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">              value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/config/</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # Spring支持 Relaxing Binding，使用 -jar xxx.jar --spring.config.location 也可以</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          command</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"java"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-jar"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"your-spring-boot-app.jar"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          volumeMounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">conf-path</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">              mountPath</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/config</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><h2 id="总结与思考" tabindex="-1">总结与思考 <a class="header-anchor" href="#总结与思考" aria-label="Permalink to &quot;总结与思考&quot;">&ZeroWidthSpace;</a></h2>
<p>本文主要介绍了我最近开发的一个实现Spring Boot动态配置的轻量级库：Spring Boot Dynamic Config，以及为什么结合Git + Kubernetes的配置管理模式，优于其他配置管理组件。</p>
<h4 id="使用场景" tabindex="-1">使用场景 <a class="header-anchor" href="#使用场景" aria-label="Permalink to &quot;使用场景&quot;">&ZeroWidthSpace;</a></h4>
<p>技术是解决问题的，<strong>脱离问题场景</strong>做选择是没有意义的。</p>
<ul>
<li>Nacos适合有历史包袱的项目、未容器化的项目，应用层不得不借助Nacos这类组件，实现一些通用能力。</li>
<li>对于已经上了Kubernetes的系统，结合DevOps的持续交付流水线，<strong>使用Spring Boot Dynamic Config是最简洁高效</strong>的解决方案。</li>
<li>当然，对于<strong>简单的场景</strong>，<strong>在3，5台机器上直接跑个位数的服务</strong>，即使没有容器化、没有DevOps流水线，直接SSH到服务器去改配置文件，这时用Dynamic Config也是比单独部署配置服务更简单的一条路。</li>
</ul>
<h4 id="少即是多" tabindex="-1">少即是多 <a class="header-anchor" href="#少即是多" aria-label="Permalink to &quot;少即是多&quot;">&ZeroWidthSpace;</a></h4>
<p>我开发这个库的动机，是在参与数十个微服务应用的DevOps工作时，看着运维同事深陷大量环境和服务的配置管理泥坑，我开始反思一个问题：<strong>配置管理有必要如此复杂吗</strong>？</p>
<p>当我们已经有了Git、有了Kubernetes，那么，Git不就是那个最完美的配置管理系统吗？Kubernetes不就是那个最完美的配置中心吗？踏破铁鞋无觅处，得来全不费工夫。</p>
<blockquote>
<p>Keep it simple and stupid !</p>
</blockquote>
<h4 id="kubernetes-x" tabindex="-1">Kubernetes + X <a class="header-anchor" href="#kubernetes-x" aria-label="Permalink to &quot;Kubernetes + X&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>Kubernetes + Git</strong>解决了复杂微服务系统的配置管理问题。其实，<strong>Kubernetes + X</strong>的组合几乎可以解决掉<strong>服务治理的所有问题</strong>。而开发人员很少了解<strong>Kubernetes这样强大的云原生平台</strong>，认为Kubernetes仅仅是部署运维的工具。我以后还会再写一些文章来说明：为什么在Kubernetes体系下，许多组件和轮子是<strong>不必要</strong>的，包括主流的<strong>Spring Cloud生态</strong>的诸多组件。之前也曾写过一个简单的回答：<a href="https://www.zhihu.com/question/430048535/answer/1582533126" target="_blank" rel="noreferrer">https://www.zhihu.com/question/430048535/answer/1582533126</a></p>
<h4 id="项目的开发历程" tabindex="-1">项目的开发历程 <a class="header-anchor" href="#项目的开发历程" aria-label="Permalink to &quot;项目的开发历程&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>为什么造这个轮子</strong>？</p>
<p>Kubernetes ConfigMap/Secret已经是主流的云原生配置方案，而<strong>Java Spring生态缺少一个动态感知ConfigMap/Secret变化的轻量级库</strong>。Spring Cloud Kubernetes (<a href="https://spring.io/projects/spring-cloud-kubernetes" target="_blank" rel="noreferrer">https://spring.io/projects/spring-cloud-kubernetes</a>) 这个库采用直接调用Kubernetes API的方案，一方面这个库过重了；另一方面与Kubernetes耦合过于紧密，启动服务还需要访问Kubernetes API的权限，不合适。</p>
<p><strong>效果如何</strong>？</p>
<p>这个小轮子一共花了一周多的业余时间，一小半的时间在解决疑难杂症，还有一小半的时间在写文档、改进单元测试和代码质量。</p>
<p>最终，精简到仅有<strong>600多行</strong>实现代码，无任何除了Spring Boot核心库以外的依赖。同时开发了<strong>400多行</strong>单元测试，测试覆盖率<strong>95%</strong>，CodeBeat代码质量评分在<strong>A/B</strong>级之间。</p>
<p>再贴一下项目地址，使用方法打开就可以看到：<a href="https://github.com/Code2Life/spring-boot-dynamic-config" target="_blank" rel="noreferrer">https://github.com/Code2Life/spring-boot-dynamic-config</a> 。<strong>欢迎小伙伴们使用、Star、PR</strong>！</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[白话并发模型和异步编程范式]]></title>
            <link>https://code2life.top/blog/0062-concurrent-model-async-programming</link>
            <guid>https://code2life.top/blog/0062-concurrent-model-async-programming</guid>
            <pubDate>Mon, 31 May 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="白话并发模型和异步编程范式" tabindex="-1">白话并发模型和异步编程范式 <a class="header-anchor" href="#白话并发模型和异步编程范式" aria-label="Permalink to &quot;白话并发模型和异步编程范式&quot;">&ZeroWidthSpace;</a></h1>
<p><img src="https://filecdn.code2life.top/blog/%2A%2Alangs-influence.png" alt=""></p>
<p>在编程领域，<strong>并发</strong>和<strong>异步</strong>这两个概念并没有初学者想象的那么高深，本文将以最普通的白话，拆解这两个概念，读完后下面这一系列问题，或许你就有了答案。</p>
<ul>
<li><strong>协程</strong>是什么？</li>
<li><strong>协程</strong>和<strong>线程</strong>的本质区别是啥？</li>
<li>哪些编程语言支持协程？<strong>有栈协程</strong>和<strong>无栈协程</strong>有什么区别？</li>
<li><strong>CSP模型</strong>是什么意思？</li>
<li><strong>Actor模型</strong>是什么意思？</li>
<li><strong>I/O多路复用</strong>到底解决了什么问题？</li>
<li>线程间<strong>上下文切换</strong>的成本真的很高吗？</li>
<li>为什么用<strong>同步原语</strong>进行并发编程常常出BUG？</li>
<li>怎么解决<strong>异步回调模式</strong>的可维护性问题？</li>
<li><strong>async/await</strong>算协程吗？和Generator函数是什么关系？</li>
<li>什么是<strong>函数响应式编程（FRP）</strong>？</li>
<li>为什么Node.js要用<strong>单线程</strong>？真的只有一个线程吗？</li>
<li>为什么Java到JDK 16还<strong>没有协程</strong>？</li>
<li>为什么更优雅的<strong>Actor模型、FRP</strong>没有成为主流？</li>
</ul>
<p>目录：</p>
<ul>
<li><a href="#白话并发模型和异步编程范式">白话并发模型和异步编程范式</a>
<ul>
<li><a href="#主流编程语言的并发模型">主流编程语言的并发模型</a>
<ul>
<li><a href="#三头六臂多线程模型">三头六臂：多线程模型</a></li>
<li><a href="#万剑归宗io多路复用--单线程模型">万剑归宗：I/O多路复用 + 单线程模型</a></li>
<li><a href="#千手观音协程模型">千手观音：协程模型</a></li>
<li><a href="#多面手-你有我有全都有">多面手: 你有我有全都有</a></li>
</ul>
</li>
<li><a href="#异步编程范式">异步编程范式</a>
<ul>
<li><a href="#异步回调模式">异步回调模式</a></li>
<li><a href="#callback的蜕变asyncawait范式">Callback的蜕变：async/await范式</a></li>
<li><a href="#callback的涅槃函数响应式编程">Callback的涅槃：函数响应式编程</a></li>
<li><a href="#cspactor模型一切皆消息">CSP/Actor模型：一切皆消息</a></li>
</ul>
</li>
<li><a href="#到底哪个范式最好">到底哪个范式最好？</a>
<ul>
<li><a href="#理论-vs-现实">理论 vs 现实</a></li>
<li><a href="#插曲-为什么java至今没有协程">插曲: 为什么Java至今没有协程</a></li>
<li><a href="#结语">结语</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="主流编程语言的并发模型" tabindex="-1">主流编程语言的并发模型 <a class="header-anchor" href="#主流编程语言的并发模型" aria-label="Permalink to &quot;主流编程语言的并发模型&quot;">&ZeroWidthSpace;</a></h2>
<p>为了方便理解，我们用餐厅打个比方，贯穿整篇文章。</p>
<p>CPU核心，或者说Processor，是<strong>厨师</strong>。</p>
<img src="https://filecdn.code2life.top/blog/%2A%2Acooker.png" width="598" />
<p>Thread是<strong>灶台</strong>。</p>
<p><img src="https://filecdn.code2life.top/blog/%2A%2Ameiqizao.png" alt=""></p>
<p><strong>理想情况下，一个厨师一个灶台，厨师一刻不停的炒菜。这种情况餐厅老板是最开心的。</strong></p>
<p>但是，客人不仅吃炒菜，也喜欢喝鸡汤。<strong>厨师</strong>（Processor）需要在一个灶台<strong>炖汤</strong>（I/O操作）的时候，换到别的灶台（Thread）炒菜，<strong>换灶台</strong>就叫<strong>线程间上下文切换</strong>。</p>
<p>于是，一系列问题就出现了。</p>
<p>我们先从大家熟悉的多线程编程开始讲。</p>
<h4 id="三头六臂-多线程模型" tabindex="-1">三头六臂：多线程模型 <a class="header-anchor" href="#三头六臂-多线程模型" aria-label="Permalink to &quot;三头六臂：多线程模型&quot;">&ZeroWidthSpace;</a></h4>
<p>典型代表：Java。</p>
<p><img src="https://filecdn.code2life.top/blog/%2A%2Asantouliubi.png" alt=""></p>
<p>哦，放错图了。</p>
<p><img src="https://filecdn.code2life.top/blog/%2A%2Asantouliubi2.png" alt=""></p>
<p>加入客人要100锅鸡汤，最朴素的办法就是放100个灶台，每个灶台炖一锅。厨师需要有<strong>三头六臂</strong>来回奔波，哪个炖了多久、哪个要加调料，都要厨师操心。</p>
<p>这种方案好像除了换灶台费点事，好像也没什么大问题。编程语言实现多线程模型，大多基于操作系统提供的原生线程，再包装一层出来给开发者用。而<strong>封装的线程</strong>和<strong>OS线程</strong>是<strong>一对一</strong>关系，线程调度<strong>完全交给操作系统</strong>就完事了，编程语言的Runtime实现也相对简单。</p>
<p>不过，当生意越来越好，老板雇两个厨师来一起干（多核并行编程），问题就出来了。对于同一锅鸡汤：</p>
<ul>
<li>1号厨师放过盐了，2号厨师不知道又放了一次，客户非常生气；</li>
<li>两个厨师都以为对方放了盐，结果都没放盐，客户非常生气；</li>
<li>1号厨师和2号厨师正巧准备一起放，两人互相扯皮了半天还没放，所有客户都非常生气。</li>
</ul>
<p>这叫<strong>竞态条件</strong>（Race Condition）。解决这类问题，需要用到各种<strong>同步原语</strong>：从CPU硬件层面的<strong>CAS指令</strong>；到OS级别的<strong>临界区、信号量、互斥量</strong>；再到编程语言的<strong>原子类型、各种锁、同步栅栏、并发安全的集合</strong>，都是让<strong>内存数据</strong>能被<strong>多核CPU安全地修改</strong>。</p>
<p>除了同步原语，有没有其他办法呢？餐厅的老板很聪明，想到了两种新的方案，下面两节分别介绍。</p>
<h4 id="万剑归宗-i-o多路复用-单线程模型" tabindex="-1">万剑归宗：I/O多路复用 + 单线程模型 <a class="header-anchor" href="#万剑归宗-i-o多路复用-单线程模型" aria-label="Permalink to &quot;万剑归宗：I/O多路复用 + 单线程模型&quot;">&ZeroWidthSpace;</a></h4>
<p>典型代表：JavaScript。</p>
<img src="https://filecdn.code2life.top/blog/%2A%2Awanjianguizong.png" width="598" />
<p>回顾上一节那个餐厅的难题：炖的汤越来越多，<strong>换灶台要时间，灶台太多放不下，厨师多了会打架</strong>。</p>
<p>餐厅老板脑袋一拍：咱就炖个汤还请那么多厨师干嘛？就雇<strong>一个厨师</strong>不就好了吗！什么，厨师忙不过来？雇勤杂工！</p>
<p>厨师放调料要1秒，炖的过程要等1小时。类比计算机世界也类似，不同部件的<strong>执行速度</strong>严重失衡：<a href="/blog/0056-performance2.html">CPU计算 &gt;&gt; 主存读写 &gt;&gt; 网络或文件I/O</a>。</p>
<ul>
<li>CPU很快：1核CPU在一眨眼的功夫，100毫秒，就可以执行数亿条指令。</li>
<li>I/O很慢：如果一次主存访问想象成1天的话，一趟局域网数据传输就要13.7年。</li>
</ul>
<p>老板雇了勤杂工之后，炖鸡的这些锅，全都交给勤杂工一起<strong>批量照看</strong>就好了，<strong>大厨只负责在恰当的时候放调料</strong>。脑补一下关东煮就明白了：</p>
<p><img src="https://filecdn.code2life.top/blog/%2A%2Aguandongzhu.png" alt=""></p>
<p>我们把每个小格子想象成一个Socket连接，这就叫<strong>I/O多路复用</strong>。</p>
<p>在餐厅里：有一个手持任务队列、名为EventLoop的大厨线程，加上N个任劳任怨的勤杂工线程一起干活。</p>
<p>以Node.js为例，厨师是这样的：</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">cooker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"该放调料了"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"一眨眼功夫就放好啦"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>苦逼的勤杂工们，被关在一个叫“libuv”的小黑屋里，切到内核态进行系统调用，干着类似这样的活：</p>
<div class="language-c++ vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c++</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 设置Socket非阻塞</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setnonblocking</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(socket_fd);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ep_fd </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> epoll_create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(max_events);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 被困在系统里的“勤杂工”</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  epoll_wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 阻塞式读取数据，交给大厨处理</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>从这个模型来看，I/O多路复用，本质上是解决了<strong>I/O与计算职责分离</strong>的问题。当<strong>网络I/O</strong>的脏活、累活分离出去了，只要一位大厨，炖百万只鸡不在话下。Node.js、Redis都是这么干的。篇幅原因，只简单说明了网络I/O的案例。定时器、文件I/O等异步任务的实现原理是<strong>不一样</strong>的，和I/O多路复用<strong>无关</strong>。</p>
<p>然鹅，这里有一个巨大的隐患。</p>
<p>如果不仅要做关东煮或者炖鸡（I/O密集型任务），还时常要做炒菜（CPU密集型任务）怎么办？</p>
<p>协程，该出场了。</p>
<h4 id="千手观音-协程模型" tabindex="-1">千手观音：协程模型 <a class="header-anchor" href="#千手观音-协程模型" aria-label="Permalink to &quot;千手观音：协程模型&quot;">&ZeroWidthSpace;</a></h4>
<p>典型代表：Erlang / Golang。</p>
<img src="https://filecdn.code2life.top/blog/%2A%2Aqianshouguanyin.png" width="598" />
<p>为了解决<strong>既要炒菜又要炖汤</strong>的问题，聪明的餐厅老板又脑袋一拍，想到一个<strong>万全之策</strong>：</p>
<ul>
<li>多雇几个厨师，但是<strong>每个人只管面前这一个灶台</strong> —— 不需要线程切换，有多少核CPU就建多少OS线程；</li>
<li>不用增加灶台，但要购置一大批锅 —— 这些“锅”就是协程（coroutine）；</li>
<li>之前雇的勤杂工继续照看炖鸡的锅和关东煮的锅 —— 继续保留 非阻塞I/O + I/O多路复用 的优良传统；</li>
<li>再雇一波勤杂工，就叫他们“<strong>换锅侠</strong>”吧，<strong>专门负责看厨师们面前的锅</strong>有没有炒好、放好调料，弄好了就立刻<strong>帮厨师换锅</strong> —— <a href="https://golang.org/src/runtime/proc.go" target="_blank" rel="noreferrer">协程调度</a>。</li>
</ul>
<p>协程最核心的特点就是：<strong>不用OS线程的上下文切换，在用户态实现超轻量的执行单元调度</strong>。协程的实现有很多，可以根据<strong>协程之间有无调用栈</strong>分为<strong>有栈协程</strong>和<strong>无栈协程</strong>；也可以根据协程间是否存在从属关系分为<strong>对称协程</strong>和<strong>非对称协程</strong>。</p>
<h4 id="多面手-你有我有全都有" tabindex="-1">多面手: 你有我有全都有 <a class="header-anchor" href="#多面手-你有我有全都有" aria-label="Permalink to &quot;多面手: 你有我有全都有&quot;">&ZeroWidthSpace;</a></h4>
<p>其实，编程语言的演进过程也是互相“借鉴”的过程，最后的结果就是“你有我有全都有”。其中最典型的“借鉴”就是Generator函数，搭配async/await或yield实现<strong>无栈协程</strong>。</p>
<p>async/await模式我们在下面讲异步编程范式再细谈，是一个兼顾实现成本、迁移成本、性能、可维护性的方案，因此多数主流编程语言都可以看到async/await协程的身影。比如：</p>
<ul>
<li>C++(20): co_await/co_return/co_yield</li>
<li>C#: async/wait, yield</li>
<li>JavaScript: async/await, yield</li>
<li>Dart: async/await, yield</li>
<li>Python：async/await, yield</li>
</ul>
<p>注：</p>
<ul>
<li>虽然C++ 20在语法层面提供的是无栈协程，C/C++ 生态也有诸多使用汇编或其他方式实现的<strong>有栈协程库</strong>。</li>
<li>执行线程是单线程的Node.js，也早已提供了多线程的支持，用于处理CPU密集型任务。</li>
</ul>
<h2 id="异步编程范式" tabindex="-1">异步编程范式 <a class="header-anchor" href="#异步编程范式" aria-label="Permalink to &quot;异步编程范式&quot;">&ZeroWidthSpace;</a></h2>
<p>上面一节我们讲了3种并发模型，基于编程语言实现的并发模型，又演化出了多种异步编程范式。下面一节，我们逐个讲解各类异步编程范式，仍然举餐厅的例子，设想一个场景：现在想让厨师做鸡汤给我们喝，需要 startBoil/coolDown/drinkSoap 3个耗时操作。</p>
<p>在讲异步编程范式之前，我们先看同步编程是怎么做的。为了保持简洁，假设都在一个线程执行了，不涉及多线程间共享数据。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Thread</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    startBoil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    coolDown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    drinkSoap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h4 id="异步回调模式" tabindex="-1">异步回调模式 <a class="header-anchor" href="#异步回调模式" aria-label="Permalink to &quot;异步回调模式&quot;">&ZeroWidthSpace;</a></h4>
<p>这三个操作要转换成异步调用模式，最直观的解决方案就是用<strong>回调函数</strong>。什么是回调函数呢？</p>
<p>声明一个函数，把函数指针丢给调度器，这次I/O搞定了就来执行它，这就是一个Callback。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">startBoil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"炖好了，撒点盐"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    coolDown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"现在可以喝了"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        drinkSoap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"嗝~~"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>虽然避免了线程间上下文切换的问题，但这样写异步代码，写着写着屏幕就不够宽了。嵌套回调越来越深，变成了回调地狱，下面几节就是常见解法。</p>
<ul>
<li>async/await/yield：熨平callback嵌套褶皱；</li>
<li>发布订阅模式：把callback丢出去不管了；</li>
<li>函数响应式编程：把callback做成烤串；</li>
<li>CSP模型/Actor模型：回归同步调用，放到有栈的协程/线程。</li>
</ul>
<h4 id="callback的蜕变-async-await范式" tabindex="-1">Callback的蜕变：async/await范式 <a class="header-anchor" href="#callback的蜕变-async-await范式" aria-label="Permalink to &quot;Callback的蜕变：async/await范式&quot;">&ZeroWidthSpace;</a></h4>
<p>async/await本质上是编程语言对generator/yield的一层语法糖。懂了Generator也就明白了async/await的原理，以及<strong>为什么Generator函数可以熨平回调函数的嵌套褶皱</strong>。</p>
<p>具体的原理分析网上有很多文章，比如这个Node.js的：<a href="https://juejin.cn/post/6844903967298682888" target="_blank" rel="noreferrer">async/await 源码实现</a></p>
<p>一句话概况就是：Generator可以看做状态机，遇到yield就进入Pending状态出让执行权，遇到resume/next就继续执行，直到下一个yield。编程语言或者SDK把generator函数包装成 async/await 关键字，就能在看似同步的代码块中异步执行，由于Generator实现的协程是在当前栈顶上继续调用函数，只能模拟携带上下文，并不是真正的保存当前上下文，切换到另一个协程栈，因此是无栈协程。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> startBoil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> coolDown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> drinkSoap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>这种看上去像同步的调用，让心智负担大大降低，也是实际工程中权衡利弊的工程中<strong>非常实用</strong>的方案。</p>
<p>不过，async/await存在一个小问题：<strong>关键字传染</strong>。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  someArray.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">forEach</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 只要调用链底存在异步，一条链全部都要带上async关键字</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>下次新入职的前端遇到&quot;SyntaxError: await is only valid in async functions&quot;报错的时候，你就可以拍拍她：写<strong>Generator状态机实现的无栈协程</strong>的时候会出现async关键字传染问题，显式告诉V8引擎，即可解决这类问题。</p>
<h4 id="callback的涅槃-函数响应式编程" tabindex="-1">Callback的涅槃：函数响应式编程 <a class="header-anchor" href="#callback的涅槃-函数响应式编程" aria-label="Permalink to &quot;Callback的涅槃：函数响应式编程&quot;">&ZeroWidthSpace;</a></h4>
<p>除了async/await/yield，异步回调地狱还有另一个解法 —— 函数响应式编程（FRP）。</p>
<p>FRP简单的理解可以认为是：</p>
<blockquote>
<p>函数响应式编程（FRP） ≈ 函数式编程（FP） + 发布订阅模式</p>
</blockquote>
<p>在讲FRP之前，我们先复习一下“发布订阅模式”。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// cooker.js</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">eventBus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"炖好了"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  eventBus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">emit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"可以喝了"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// eater.js</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">eventBus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"可以喝了"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"嗝~~"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"对了，汤咋做出来的，炖汤之前嘎哈了？"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>发布订阅模式在异步编程中，从另一个维度解决掉了回调地狱问题。让<strong>Event Bus统一管理一大锅Callback</strong>，每个Callback挂了一个onXXXEvent的标签，来了什么异步事件，就让Event Bus统一来调用对应的函数。</p>
<p>既然一个Event Bus就解决了回调地狱问题，为什么还要函数响应式编程呢？</p>
<p>因为事件模式，解开了Callback，会带来<strong>逻辑碎片化</strong>的问题。也就是说，完全靠Event Listener无法写出高内聚的代码。</p>
<p>那有没有办法，把<strong>碎片化</strong>的回调函数整合起来，让代码重新内聚呢？</p>
<p>有的。萝卜加大棒，听说<strong>发布订阅模式</strong>和<strong>函数式编程</strong>更配哦？</p>
<p>函数式编程（FP）是一个自古有之的概念，把一沓纯净的函数声明式地组合起来，理论上就可以实现任何功能。事件驱动的异步回调函数，经过FP的洗礼，变成了Callback烤串，外酥里嫩。</p>
<p>我理解的函数响应式编程：就是通过一系列<strong>操作符</strong>对函数<strong>组合</strong>，实现复杂的<strong>异步事件流的操纵和处理</strong>，异步事件与函数式编程的完美结合。</p>
<p>比如下面是一段用RxJS实现异步事件流处理的代码实例，没有回调地狱，也不需要async/await。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// incoming$ is a stream that flows</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">MessageBus.incoming$.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pipe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // delay messages after call pauseDispatcher()</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  delayWhen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pauseWhenUnAvailable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // dispatch input messages, transform input stream to output stream</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  mergeMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">input</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">handleCommand</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(input)),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // publish output messages</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  mergeMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">output</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sendAckIfNeed</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(output)),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // record output</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  tap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">output</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">logOutput</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(output);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">subscribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // do something</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>脑补一下植物大战僵尸游戏里，biubiubiu的豌豆射手，源源不断的豌豆异步发射出来，经过火炬的Pipeline变成了火豌豆，最终真正起作用是在砸到目标的瞬间，也就是上面subscribe里的逻辑。</p>
<p><img src="//filecdn.code2life.top/zombie.png" alt=""></p>
<p>FRP范式下经常提到背压控制，我们脑补一下水坝，上游的水流速度时快时慢，但水坝可以缓冲整流，让下游流速非常平缓，在传统编程范式要实现复杂的整流逻辑挺复杂的，而在Rx中实现“水坝”功能，仅仅需要一个操作符。这种操作符组合的黑魔法，尤其适合作为框架层的实现基础。因此，大家熟知的Java界新秀：Vert.x, WebFlux 等框架，前端的Angular/Vue/React框架都有FRP的影子。</p>
<h4 id="csp-actor模型-一切皆消息" tabindex="-1">CSP/Actor模型：一切皆消息 <a class="header-anchor" href="#csp-actor模型-一切皆消息" aria-label="Permalink to &quot;CSP/Actor模型：一切皆消息&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Do not communicate by sharing memory; instead, share memory by communicating.</p>
</blockquote>
<p>上面这句名言，Share Nothing架构，也解释了CSP模型和Actor模型的共性：把编程问题转换为通信问题，不同的执行单元<strong>不共享同一份内存数据</strong>，因此就不需要任何同步原语控制共享数据的访问。</p>
<p>我们先说CSP模型，CSP是上个世纪七十年代提出的，用于描述<strong>两个独立的并发实体</strong>通过<strong>共享的 channel 进行通信</strong>的并发模型。Golang用channel炖鸡汤的代码如下：</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  boiled </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">chan</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  drinkable </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">chan</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  finished </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">chan</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  go</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"开始炖</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time.Second)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"炖好了</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    boiled </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;-</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}{}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  go</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    &#x3C;-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">boiled</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"凉一凉</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time.Second)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"凉好啦，可以喝了</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    drinkable </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;-</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}{}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  go</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    &#x3C;-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">drinkable</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"喝完啦</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    finished </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;-</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}{}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  &#x3C;-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">finished</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><p>注：Golang虽然部分实现了CSP模型，但语言本身也<strong>允许共享内存数据</strong>，如果不用channel机制，直接多协程更新共享数据，不正确使用<strong>同步原语</strong>也一样会出现并发BUG。</p>
<p>至于常常一起被提到的Actor模型，我们常说Erlang/OTP、Scala-Akka就是典型的Actor模型（虽然Erlang的诞生比Actor模型概念的提出更早，Erlang的作者也不认为Erlang是Actor模型），其主要特点也是在于把<strong>编程问题转换为通信问题</strong>：</p>
<ul>
<li>Actor之间<strong>完全不存在共享数据</strong>，创建Actor非常廉价；</li>
<li>每个Actor，在Erlang中叫微进程，有自己的执行栈，互相之间<strong>完全隔离</strong>；</li>
<li>每个Actor有一个“邮箱”， Actor之间通过收发邮箱通信。</li>
</ul>
<p>与CSP模型不同的是，<strong>Actor模型</strong>更进一步，<strong>每个独立的并发实体</strong>都有一份自己的“Channel”，在Erlang中叫“具名邮箱”。可以看出，这种抽象非常适合消息相关的领域，比如曾经WhatsApp增长到9亿用户也只有50人维护的聊天服务器、Zoom的聊天服务器、一些著名的分布式消息队列组件，都是用Erlang开发的。</p>
<p>因此，简单的理解Actor模型就是：有栈协程/线程 + 发布订阅模式 + Share-nothing 架构。CSP模型与Actor更细节的原理可以阅读这篇文章：<a href="//jolestar.com/parallel-programming-model-thread-goroutine-actor/" target="_blank" rel="noreferrer">并发之痛 Thread，Goroutine，Actor</a></p>
<p>结合对OOP和FRP的理解，我自创了一个词来概括CSP/Actor范式 —— 面向对象响应式编程（OORP）。OORP可以看作是原教旨面向对象编程在异步事件流场景下的特化产物。</p>
<h2 id="到底哪个范式最好" tabindex="-1">到底哪个范式最好？ <a class="header-anchor" href="#到底哪个范式最好" aria-label="Permalink to &quot;到底哪个范式最好？&quot;">&ZeroWidthSpace;</a></h2>
<p>我们从主流编程语言的并发模型出发，了解了几类异步编程范式及其演化历程，学习了5种“喝鸡汤”的姿势。那么，这些并发模型下的异步编程范式，哪个最好？</p>
<h4 id="理论-vs-现实" tabindex="-1">理论 vs 现实 <a class="header-anchor" href="#理论-vs-现实" aria-label="Permalink to &quot;理论 vs 现实&quot;">&ZeroWidthSpace;</a></h4>
<p>理论上，上面介绍的4种范式中：函数响应式编程、Sharing noting的Actor模式 似乎是最优雅的解决方案；
实际上，目前世界上大部分代码，都是在用同步编程或Async/Await的假装同步，这两个看似一堆缺点的方案。</p>
<p><strong>为什么会这样呢？</strong></p>
<p>其原因我们从FP的发展历程可以看出来。猿界有一支神秘的学院派<strong>函数式编程</strong>的崇拜者，念叨着函子，单子、纯函数、柯里化之类的咒语，膜拜Lisp、Pascal，鄙视新泽西派简陋的C、C++、Golang、蓝领语言Java。从工程师的视角看，FP的确在一些基础库和特定领域解决了非常关键的问题，但很难成为软件系统中的砖头和水泥。</p>
<p>类似的，Actor模型、FRP这类技术或许一直将是小众选型，因为：</p>
<ul>
<li>世界是不确定的：世界充满不确定的变化，无法用完美的模型来表示，打补丁才是常态；</li>
<li>人脑带宽有限：当一种知识，学习它的心智成本过高，学习它的人数就会呈<strong>幂律分布</strong>骤减。</li>
</ul>
<p>现实世界常常是 <a href="https://en.wikipedia.org/wiki/Worse_is_better" target="_blank" rel="noreferrer">Worse is better</a>。能解决掉实际问题的技术，就会有市场，不管我们是认为它们是好还是坏、优雅还是丑陋，即使是被诟病的回调模式也有应用场景。</p>
<h4 id="插曲-为什么java至今没有协程" tabindex="-1">插曲: 为什么Java至今没有协程 <a class="header-anchor" href="#插曲-为什么java至今没有协程" aria-label="Permalink to &quot;插曲: 为什么Java至今没有协程&quot;">&ZeroWidthSpace;</a></h4>
<p>广泛使用的Java就是线程池、JUC类库撸到底，协程是什么，我不听，我不听。Java官方的协程特性支持（Project Loom）从JDK 14就说要发布了，难产了好几个大版本，至今还没生出来。</p>
<p>那么，为什么Java头这么铁，是道德的沦丧还是人性的扭曲？</p>
<p>其实也不能怪Java，这里有一系列很棒的回答：<a href="https://www.zhihu.com/question/332042250/answer/734115120" target="_blank" rel="noreferrer">为什么Java坚持多线程不选择协程？</a>，总结一下主要原因有：</p>
<ul>
<li>数据库操作无法协程化，20多年的JDBC标准就是同步的、一个连接一个线程。其他部分花里胡哨的NIO/Reactor也没法根除线程池模式，除非不用JDBC；</li>
<li>大部分网络I/O已经被Netty们剥离出去，性能瓶颈问题已经解决大半，JDBC的数据库I/O剥不出去也没太大问题，毕竟大部分数据库自己就处理不了太高并发；</li>
<li>同步编程的业务线程池就算切换白耗一点CPU又咋地了，毕竟JVM已经那么吃内存了，Spring全家桶各种AOP、反射的损耗已经那么多了，不在乎再多耗一点；</li>
<li>同步编程模式符合直观思维模式，已经深入广大JAVAer的心，Reactor/ReactiveX那一套即使学会了，习惯了传统的Java编程模式的开发者用起来也别扭；</li>
<li>历史包袱太沉，核心生态对线程池、ThreadLocal、JDBC这些东西的依赖太强，迁移成本很高，从Project Loom的Virtual Thread的设计也可以看出来；</li>
<li>其实<a href="https://blog.csdn.net/guangcheng0312q/article/details/110358905" target="_blank" rel="noreferrer">线程上下文切换的开销</a>也没有那么恐怖，现代CPU可以做到<strong>约每秒33万次线程切换</strong>，一次耗时<strong>约3μs</strong>，即使相比于Golang的协程切换慢了30倍，这些开销也可以接受。</li>
</ul>
<p>没有协程的Java活的很好。其实多线程模型下，内存数据共享也不是原罪，关键在于数据共享时，执行上下文<strong>自动</strong>被外部调度器切换了才是BUG之源，于是需要依靠<strong>同步原语</strong>和<strong>头发稀疏程度</strong>来保障不出BUG。</p>
<p>因此，Java的面试总要问成堆的并发、同步栅栏、锁、线程池问题，JavaScript的面试就不会。</p>
<h4 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h4>
<p>哪个语言的并发模型最好、哪种异步编程范式最好，不会存在标准答案。</p>
<p>对于编程语言和范式的选择，也不一定是单选题。实际开发中，我们完全可以混合范式编程，对特定的业务类型应用特定的编程范式，找最优解对付现实问题。</p>
<p>从异步编程范式，归结到面向对象编程与函数式编程，这二者像是<strong>编程领域的的波粒二象性</strong>。<strong>面向对象是粒，函数式是波</strong>：面向对象更关注数据结构，强调信息隐藏、消息传递；而函数式编程更关注行为，由变而生、一切皆函数。</p>
<blockquote>
<p>不管黑猫白猫，抓到老鼠就是好猫。</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[如何使用Gradle管理多模块Java项目]]></title>
            <link>https://code2life.top/blog/0060-gradle-multi-module</link>
            <guid>https://code2life.top/blog/0060-gradle-multi-module</guid>
            <pubDate>Thu, 29 Apr 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="如何使用gradle管理多模块java项目" tabindex="-1">如何使用Gradle管理多模块Java项目 <a class="header-anchor" href="#如何使用gradle管理多模块java项目" aria-label="Permalink to &quot;如何使用Gradle管理多模块Java项目&quot;">&ZeroWidthSpace;</a></h1>
<p>由于项目需要，我最近学习了一点关于Gradle的<strong>入门知识</strong>，本篇以一个<strong>含有多个子项目的Java SpringBoot工程</strong>为例，总结分享一下<strong>Gradle的基本原理和正确使用方式</strong>。</p>
<p>写这篇文章，主要因为网上查阅到的Gradle相关博客和中文文档<strong>几乎都已经过时</strong>了，Gradle官方文档很靠谱但是是英文的，我读了一些文档，结合自己的理解尽量写的深入浅出一些，适合Java开发看。但是要系统学习Gradle还得看<a href="https://docs.gradle.org/current/userguide/getting_started.html" target="_blank" rel="noreferrer">官方文档</a>。</p>
<h2 id="gradle从陌生到入门七问" tabindex="-1">Gradle从陌生到入门七问 <a class="header-anchor" href="#gradle从陌生到入门七问" aria-label="Permalink to &quot;Gradle从陌生到入门七问&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="gradle是什么" tabindex="-1">Gradle是什么？ <a class="header-anchor" href="#gradle是什么" aria-label="Permalink to &quot;Gradle是什么？&quot;">&ZeroWidthSpace;</a></h4>
<p>Gradle是一个以Groovy/Kotlin作为DSL的<strong>灵活的</strong>，<strong>可扩展的</strong>，高性能<strong>构建工具</strong>。广泛应用于多种语言和框架的项目构建，比如Android项目、Java类库和后端项目、多编程语言的项目等等</p>
<h4 id="gradle与maven最本质的区别" tabindex="-1">Gradle与Maven最本质的区别？ <a class="header-anchor" href="#gradle与maven最本质的区别" aria-label="Permalink to &quot;Gradle与Maven最本质的区别？&quot;">&ZeroWidthSpace;</a></h4>
<p>Maven的目的是用XML描述项目的<strong>项目管理工具</strong>，而Gradle是用DSL描述Build Task的<strong>自动化构建工具</strong>。虽然功能有重合的部分，但根本上它俩的思想和机制是完全不一样的。</p>
<h4 id="为什么java项目推荐用gradle替换maven" tabindex="-1">为什么Java项目推荐用Gradle替换Maven？ <a class="header-anchor" href="#为什么java项目推荐用gradle替换maven" aria-label="Permalink to &quot;为什么Java项目推荐用Gradle替换Maven？&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>Gradle比Maven<strong>快</strong>；</li>
<li>Gradle默认使用的DSL是定制的Groovy，比XML<strong>简洁</strong>；</li>
<li>Gradle的插件机制比Maven更<strong>方便灵活</strong>；</li>
<li>Gradle的生态比Maven更加<strong>广泛</strong>，支持的编程语言和技术平台非常多样。</li>
</ul>
<h4 id="gradle-wrapper是什么" tabindex="-1">Gradle Wrapper是什么？ <a class="header-anchor" href="#gradle-wrapper是什么" aria-label="Permalink to &quot;Gradle Wrapper是什么？&quot;">&ZeroWidthSpace;</a></h4>
<p>由于Gradle的版本迭代很快，为了能更方便管理Gradle自身的版本，并且让不同项目之间的Gradle版本<strong>隔离开</strong>不会互相影响，Gradle项目最好使用Wrapper模式。</p>
<p>使用Wrapper模式时，项目根目录有一个CMD/Shell<strong>脚本（gradlew）<strong>用来启动Gradle Wrapper，根据</strong>gradle-wrapper.properties</strong>的定义，wrapper从CDN下载对应的Gradle版本并执行构建，免去了开发人员自己去下载和配置Gradle环境变量的负担。</p>
<p>如果在当前项目中<strong>升级Gradle的版本</strong>，执行下面这行命令即可：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradlew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wrapper</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --gradle-version</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 7.0</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>使用Wrapper之后，本地也<strong>不需要事先安装和配置gradle命令，直接用项目文件中的gradlew命令</strong>即可。</p>
<h4 id="gradle的基本概念有哪些" tabindex="-1">Gradle的基本概念有哪些？ <a class="header-anchor" href="#gradle的基本概念有哪些" aria-label="Permalink to &quot;Gradle的基本概念有哪些？&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>三级数据模型：Build，Project，Task。<strong>一个Build可以包括1到N个Project，一个Project包括1-N个Task</strong>；</li>
<li>Task之间的关系是<strong>有向无环图</strong>（DAG），构建过程就是按照DAG依次执行其中的Task，Gradle可以看成<strong>指挥官</strong>，是<strong>Task的调度器</strong>；</li>
<li>Plugin是底下真正<strong>干活的</strong>，不同的Plugin<strong>实现不同功能的Task</strong>，添加Plugin就会在Build中预定义Task和Configuration。</li>
</ul>
<p>下图即是一个常规的Java项目Build过程经历的Task，像jar/classes等等这些Task的实现就是<strong>Java Plugin</strong>提供的。</p>
<p><img src="//filecdn.code2life.top/gradle-dag.png" alt=""></p>
<h4 id="gradle的命令怎么用" tabindex="-1">Gradle的命令怎么用？ <a class="header-anchor" href="#gradle的命令怎么用" aria-label="Permalink to &quot;Gradle的命令怎么用？&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 执行名为build的Task</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 执行build Task时，跳过 test Task</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 执行时开启build cache和并行模式，减少构建时间</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --parallel</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --build-cache</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 执行完把构建数据上传到Gradle云服务，在浏览器中直接分析，如下图所示</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># --scan需要手动同意，首次激活链接后就可以看到构建报告了，功能非常强大</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --parallel</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --build-cache</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --scan</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 注： wrapper模式下，执行的是gradlew而非gradle</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>在线的Scan Report，可以看到每个Task的耗时、依赖树、单元测试失败的栈轨迹等等，花钱买Gradle Enterprise可解锁更强的功能哦。</p>
<p><img src="//filecdn.code2life.top/scan-report.png" alt=""></p>
<h4 id="gradle的原理和核心流程是什么" tabindex="-1">Gradle的原理和核心流程是什么？ <a class="header-anchor" href="#gradle的原理和核心流程是什么" aria-label="Permalink to &quot;Gradle的原理和核心流程是什么？&quot;">&ZeroWidthSpace;</a></h4>
<p>这个问题我们用<strong>示例</strong>来说明，我们创建一个简单的SpringBoot Web工程，包含3个子项目：</p>
<ul>
<li>application-api：是导出给其他服务用于RPC调用的模块；</li>
<li>application-core：是这个Web工程的核心实现模块，包括controller/service/model等等MVC结构的常用包；</li>
<li>application-boot：是SpringBoot的启动模块，包括启动类和Spring的JavaConfig配置类等等。</li>
</ul>
<p>其<strong>目录结构</strong>如下（build.gradle里面具体内容在最后一节贴出详细代码来分析）：</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>├─build.gradle            # 描述根项目的依赖和构建的核心文件</span></span>
<span class="line"><span>├─settings.gradle         # 描述根目录和子项目的关键信息</span></span>
<span class="line"><span>├─gradlew                 # Wrapper的shell script</span></span>
<span class="line"><span>├─gradle.bat              # Wrapper的cmd script</span></span>
<span class="line"><span>├─application-api</span></span>
<span class="line"><span>│  ├─build.gradle         # 描述子项目的依赖和构建等信息</span></span>
<span class="line"><span>│  └─src</span></span>
<span class="line"><span>│      └─main</span></span>
<span class="line"><span>│         └─java</span></span>
<span class="line"><span>│            └─com.example.x</span></span>
<span class="line"><span>├─application-boot</span></span>
<span class="line"><span>│  ├─build.gradle         # 描述子项目的依赖和构建等信息</span></span>
<span class="line"><span>│  └─src</span></span>
<span class="line"><span>│      ├─main</span></span>
<span class="line"><span>│      │  ├─java</span></span>
<span class="line"><span>│      │  │  └─com.example.x</span></span>
<span class="line"><span>│      │  └─resources</span></span>
<span class="line"><span>│      └─test</span></span>
<span class="line"><span>│          └─java</span></span>
<span class="line"><span>│             └─com.example.x</span></span>
<span class="line"><span>├─application-core</span></span>
<span class="line"><span>│  ├─build.gradle         # 描述子项目的依赖和构建等信息</span></span>
<span class="line"><span>│  └─src</span></span>
<span class="line"><span>│      └─main</span></span>
<span class="line"><span>│         └─java</span></span>
<span class="line"><span>│            └─com.example.x</span></span>
<span class="line"><span>└─gradle</span></span>
<span class="line"><span>    └─wrapper</span></span>
<span class="line"><span>	     ├─gradle-wrapper.jar         # 一个很轻量的可执行jar，用于下载Gradle</span></span>
<span class="line"><span>	     └─gradle-wrapper.properties  # 描述gradle版本和下载相关配置</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br></div></div><p>项目我们建好了，当我们执行<strong>gradlew clean build -x test</strong>就可以构建出来一个可执行的SpringBoot工程的jar包了。<strong>那么执行这行命令时，到底发生了什么</strong>？</p>
<ol>
<li><strong>gradlew</strong>用Java命令启动<strong>gradle-wrapper.jar</strong>，gradle尝试复用或创建一个指定版本的gradle，下载解压后，启动<strong>gradle daemon</strong>后台服务；</li>
<li>gradle扫描当前目录下的<strong>gradle.properties</strong>，用户目录（~/.gradle）下的<strong>gradle.properties</strong>等配置参数；</li>
<li>寻找当前目录下的<strong>settings.gradle</strong>，build.gradle等文件，分析并魔改Groovy/Kotlin文件的<strong>抽象语法树</strong>，构建Task执行的步骤；</li>
<li>如果settings.gradle文件<strong>include</strong>了其他子项目，继续找到对应的目录里的<strong>build.gradle</strong>等文件并分析内容，在这里就是application-boot/application-core/application-api三个子项目；</li>
<li>如果依赖的特定版本的插件或库缺失，会到gradle/maven中心仓库或自定义仓库，<strong>下载</strong>缺少的<strong>Plugin</strong>和<strong>Dependency</strong>；</li>
<li>开始按照流程图执行 'clean' Task，成功后再执行 'build' Task，由于参数-x指定了跳过'test' Task，DAG中的 'test' Task的子节点全部被排除无需执行，下面即是我们这个示例中的Task顺序图。</li>
</ol>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>:build                                                         </span></span>
<span class="line"><span>+--- :assemble                                             </span></span>
<span class="line"><span>|    +--- :bootJar </span></span>
<span class="line"><span>|    |    +--- :classes                                        </span></span>
<span class="line"><span>|    |    |    +--- :compileJava                               </span></span>
<span class="line"><span>|    |    |    |    `--- :application-boot:compileJava         </span></span>
<span class="line"><span>|    |    |    |         +--- :application-api:compileJava     </span></span>
<span class="line"><span>|    |    |    |         `--- :application-core:compileJava    </span></span>
<span class="line"><span>|    |    |    |              `--- :application-api:compileJava</span></span>
<span class="line"><span>|    |    |    `--- :processResources                          </span></span>
<span class="line"><span>|    |    +--- :application-api:jar</span></span>
<span class="line"><span>|    |    +--- :application-boot:jar</span></span>
<span class="line"><span>|    |    `--- :application-core:jar</span></span>
<span class="line"><span>|    `--- :jar                                                 </span></span>
<span class="line"><span>|         `--- :classes                                        </span></span>
<span class="line"><span>|              +--- :compileJava                       </span></span>
<span class="line"><span>|              `--- :processResources</span></span>
<span class="line"><span>|              .......  # 省略了一些输出                     </span></span>
<span class="line"><span>`--- :check                                                    </span></span>
<span class="line"><span>     `--- :test  # 未被其他Task使用的子节点已经被排除</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><p>注：上面的执行流程也是<strong>org.barfuin.gradle.taskinfo</strong>这个Gradle Plugin提供的 'tiTree' Task生成的，另外，由于我们使用了SpringBoot Plugin，assemble Task中执行的是 'bootJar' Task，而不是一节图中的 'jar' Task。</p>
<h2 id="java-gradle项目实战指南" tabindex="-1">Java + Gradle项目实战指南 <a class="header-anchor" href="#java-gradle项目实战指南" aria-label="Permalink to &quot;Java + Gradle项目实战指南&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="加依赖的正确方式" tabindex="-1">加依赖的正确方式 <a class="header-anchor" href="#加依赖的正确方式" aria-label="Permalink to &quot;加依赖的正确方式&quot;">&ZeroWidthSpace;</a></h4>
<p>对于开发人员来说，不管是Maven还是Gradle，最常用的功能就是<strong>二方/三方库的依赖管理</strong>，我们从最简单的场景开始。大多数情况下，Gradle定义依赖只需要在<strong>dependencies闭包</strong>里写一行，比如这样的：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">implementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter:2.4.5'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 不要再使用 compile 'xxx' 了</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>注意：<strong>compile指令早在Gradle 3.x版本就已经弃用</strong>，在Java项目中使用compile会导致<strong>依赖传递（Transitive Dependency）</strong>，也就是在A中定义的compile依赖，会传递到依赖了A的B项目<strong>编译期间</strong>的Classpath，污染了B项目。</p>
<p>我们再考虑一些复杂的情况：有时候，我们又想要依赖传递出去；有时候，我们想某个库对于依赖方不是必须的；有时候只想一些依赖在单元测试的时候编译进去...</p>
<ul>
<li><strong>api指令</strong>: 声明一个<strong>传递依赖</strong>，等价于之前的compile，也等价于Maven的<strong>compile scope</strong>，慎用；</li>
<li><strong>runtimeOnly指令</strong>: 相当于Maven的<strong>runtime scope</strong>，仅运行时需要的jar,比如MySQL Connector, Logback/Log4j等；</li>
<li><strong>compileOnly指令</strong>: 仅添加到编译时的classpath中，类似于Maven的<strong>provided scope</strong>，不会打包到最终产物中, 比如<strong>lombok</strong>适合用compileOnly；</li>
<li><strong>annotationProcessor指令</strong>: JSR-269引入了编译期注解处理器, 在Java 1.6之后提供了这个修改源码AST的机会，我们熟悉的lombok用的就是这个原理，需要搭配compileOnly使用；</li>
<li><strong>testXXX指令</strong>: 仅Test Task执行的指令，类似Maven的<strong>test scope</strong>, Gradle对test依赖的定义更加细致，包括<strong>testImplementation, testCompileOnly</strong>等细分指令。</li>
</ul>
<p>注：有一些Java相关的添加依赖指令，比如api、annotationProcessor等，是<strong>Java Plugin</strong>实现的，因此需要<strong>事先在build.gradle声明</strong>：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 在build.gradle的开头声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">plugins {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    id </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'java-library'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 注意：</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 1. apply plugin是过时的写法，尽量不要这样用了: apply plugin: 'java-library' </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 2. java plugin功能比较基础，使用 'java-library' 代替 'java'，功能更健全</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h4 id="指定仓库" tabindex="-1">指定仓库 <a class="header-anchor" href="#指定仓库" aria-label="Permalink to &quot;指定仓库&quot;">&ZeroWidthSpace;</a></h4>
<p>指定仓库也是常见操作，比如添加企业内部Maven仓库。在单项目工程中，可以直接在<strong>build.gradle</strong>以及<strong>buildscript</strong>里声明中心仓库。</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">repositories {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    mavenCentral()</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 参考文档：https://docs.gradle.org/current/userguide/declaring_repositories.html</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    maven {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        url </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"//awesome.com/maven"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>对于多项目工程/Monorepo，可以在<strong>allprojects/subprojects</strong>闭包中声明repositories，也可以把这段脚本抽到单独的文件，使用<strong>apply from</strong>的方式引入到<strong>所有需要的地方</strong>：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">apply </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"path/to/repositories.gradle"</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h4 id="解决依赖冲突" tabindex="-1">解决依赖冲突 <a class="header-anchor" href="#解决依赖冲突" aria-label="Permalink to &quot;解决依赖冲突&quot;">&ZeroWidthSpace;</a></h4>
<p>依赖冲突问题想必大家都遇到过，解决这类问题，首先需要分析依赖树，Gradle提供了相应的Task:</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如果没用wrapper模式，直接使用gradle命令</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradlew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependencies</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>结果中可以看到整个项目的依赖树，如果需要<strong>定点查看某个库</strong>的依赖情况，执行<strong>dependencyInsight</strong>命令，需要注意--configuration参数的区别。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 编译期classpath中，依赖库的版本分析</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradlew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependencyInsight</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --configuration</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> compileClasspath</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dependency</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> org.slf4j:slf4j-api</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 最终打包到runtime的classpath中，依赖库的版本分析</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 大部分情况下我们想分析最终打到jar/war中的三方库版本，configuration是runtimeClasspath</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">gradlew</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependencyInsight</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --configuration</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> runtimeClasspath</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dependency</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> spring-boot-starter-actuator</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>下面是我们对slf4j-api的依赖分析的截取结果，第一行的含义就是即使logback-classic库依赖的是<strong>1.7.25</strong>版本的slft4j-api，由于其他库依赖了<strong>1.7.30</strong>这个更高版本的slf4j-api，最终使用的是<strong>1.7.30</strong>。</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>org.slf4j:slf4j-api:1.7.25 -> 1.7.30</span></span>
<span class="line"><span>+--- ch.qos.logback:logback-classic:1.2.3</span></span>
<span class="line"><span>|    +--- org.springframework.boot:spring-boot-dependencies:2.4.5</span></span>
<span class="line"><span>|    |    +--- runtimeClasspath</span></span>
<span class="line"><span>|    |    +--- project :application-boot</span></span>
<span class="line"><span>|    |    |    \--- runtimeClasspath</span></span>
<span class="line"><span>|    |    +--- project :application-core</span></span>
<span class="line"><span>|    |    |    \--- project :application-boot (*)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>用dependency/dependencyInsight找到根因后，解决依赖问题通常的做法，是<strong>排除某些有问题的依赖，或者强制指定某个库的版本</strong>。</p>
<ul>
<li><strong>全局强制排除依赖</strong>，在build.gradle中直接写全局的以来排除configuration，如果是多项目工程可以在对应的<strong>build.gradle</strong>或者<strong>allprojects/subprojects</strong>闭包下写这段</li>
</ul>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 全局排除tomcat，比如换jetty/undertow就可以用这种方式避免tomcat打包进去</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">configurations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">all {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  exclude </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'spring-boot-starter-tomcat'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 也可以使用单个指令configuration的exclude，只在implementation指令生效</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">configurations {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  implementation {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    compile</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">exclude </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">group</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.bad'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> module </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'bad-module'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><ul>
<li><strong>单点排除依赖</strong></li>
</ul>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 排除某个依赖中，它依赖的其他有问题库</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">implementation(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  exclude </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'bad-module'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>强制某个依赖使用某版本</strong></li>
</ul>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">configurations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">all {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  resolutionStrategy {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 检测到依赖冲突，直接失败，而不是自动选择一个版本</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 一般Java Web项目不需要开启这个强制检查</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // failOnVersionConflict()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 强制指定某依赖库的版本</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    force(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'net.bytebuddy:byte-buddy:1.10.22'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    force(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.slf4j:slf4j-api:1.7.30'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>最后，验证问题是否已经解决，可以从下面几个方面入手：</p>
<ol>
<li>再次执行依赖分析的命令；</li>
<li>从最终构建产物直接分析，对于SpringBoot工程，把最终构建的jar解压，在BOOT-INF/lib下找到三方库，确定某个库打包版本是否正确；</li>
<li>在部署运行后，使用arthas等诊断工具寻找运行期间加载的类。</li>
</ol>
<h4 id="可选依赖" tabindex="-1">可选依赖 <a class="header-anchor" href="#可选依赖" aria-label="Permalink to &quot;可选依赖&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>Gradle没有Maven中的Optional Dependency</strong>, 因为Maven的Optional是一个有问题的设计，比如A声明需要Optional的库C，这时C不会传递给依赖A的项目B，B项目就很容易出现各种找不到类、找不到方法的报错，写Java的同学一定经历过这类依赖问题导致的报错。</p>
<p>这个场景中，问题的本质在于：难道要让B去了解三方库A里，有哪些依赖是需要手动加到自己项目的，哪些是不需要手动加的？强迫依赖方B去了解三方库A的<strong>实现细节</strong>，学过软件工程的人都知道这不是一个正确的设计。</p>
<p>Gradle设计了一个<strong>语义化</strong>更强的Feature+Compability方案，参考文档：<a href="https://blog.gradle.org/optional-dependencies%E3%80%82%E5%A4%A7%E6%A6%82%E6%B5%81%E7%A8%8B%E6%98%AF%E8%BF%99%E6%A0%B7%E7%9A%84%EF%BC%9A" target="_blank" rel="noreferrer">https://blog.gradle.org/optional-dependencies。大概流程是这样的：</a></p>
<ol>
<li>类库A的gradle文件中声明提供Feature C： java { registerFeature('featureC') {}</li>
<li>A的dependencies闭包这样写：dependencies { featureCImplementation('some-lib-required-for-feature-c') }</li>
<li>依赖方B项目在依赖时声明是否需要该Feature：implementation('some-group:lib-A:1.0.0') {  capabilities { requireCapability('featureC') } }</li>
</ol>
<h4 id="发布到maven仓库" tabindex="-1">发布到Maven仓库 <a class="header-anchor" href="#发布到maven仓库" aria-label="Permalink to &quot;发布到Maven仓库&quot;">&ZeroWidthSpace;</a></h4>
<p>Maven-Publish Plugin可以实现发布jar到Maven仓库，具体参考文档：<a href="https://docs.gradle.org/current/userguide/publishing_maven.html%E3%80%82" target="_blank" rel="noreferrer">https://docs.gradle.org/current/userguide/publishing_maven.html。</a></p>
<h4 id="全套组件的统一依赖管理" tabindex="-1">全套组件的统一依赖管理 <a class="header-anchor" href="#全套组件的统一依赖管理" aria-label="Permalink to &quot;全套组件的统一依赖管理&quot;">&ZeroWidthSpace;</a></h4>
<p>在Maven中有<strong>MavenBOM</strong>（Bill of Materials），pom.xml的dependencyManagement声明整套组件的BOM，<strong>所有相关组件都无需写特定的版本号</strong>，对于Spring全家桶，SpringBoot全家桶，SpringCloud全家桶这类生态型技术平台的依赖管理非常有用。Gradle有没有类似的功能呢？网上搜索的资料大多是这样的：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencyManagement {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  imports {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      mavenBom </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.springframework.boot:spring-boot-dependencies:2.4.5"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>这是<strong>已经过时</strong>的用法，<strong>新项目就别再用这种方式了</strong>！Gradle 5.0之后支持的原生的<strong>platform</strong>指令更加简洁。</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 技术平台的*使用方*，仅需添加一行 platform() 即可管理一组依赖约束</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  api platform(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"com.ecosystem:some-platform:1.0.0"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>如果是在中大型企业中，架构组可能会自己做技术中台，比如开发一套内部生态组件，对于生态系统的<strong>开发方</strong>，如何确保多个组件之间的<strong>依赖约束</strong>呢？</p>
<p>答案是使用<strong>Java-Platform Plugin</strong>，具体使用方式这里不再展开，参考文档：<a href="https://docs.gradle.org/current/userguide/java_platform_plugin.html" target="_blank" rel="noreferrer">https://docs.gradle.org/current/userguide/java_platform_plugin.html</a></p>
<h4 id="springboot-springcloud组件的依赖约束" tabindex="-1">SpringBoot/SpringCloud组件的依赖约束 <a class="header-anchor" href="#springboot-springcloud组件的依赖约束" aria-label="Permalink to &quot;SpringBoot/SpringCloud组件的依赖约束&quot;">&ZeroWidthSpace;</a></h4>
<p>上面已经提到，对于全家桶式的一套组件，应该使用Gradle的<strong>Platform</strong>特性，具体代码如下：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 如果用到SpringCloud，是对SpringBoot的版本有要求的，版本映射关系在SpringCloud官网有说明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">buildscript {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ext {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        springBootVersion </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '2.4.5'</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // SpringCloud 2020.0.2 全家桶，只能使用SpringBoot 2.4.x</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        springCloudVersion </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '2020.0.2'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 注意字符串用到 ${var} 的时候，一定要用双引号，这是groovy的语法决定的</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  api platform(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.springframework.boot:spring-boot-dependencies:${springBootVersion}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  api platform(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><h4 id="springboot-plugin的原理" tabindex="-1">SpringBoot Plugin的原理 <a class="header-anchor" href="#springboot-plugin的原理" aria-label="Permalink to &quot;SpringBoot Plugin的原理&quot;">&ZeroWidthSpace;</a></h4>
<p>SpringBoot Plugin会给Gradle加一个<strong>bootJar Task</strong>, 在打jar包时把设置的mainClass、SpringBoot JarLauncher等信息到<strong>META-INF/MANIFEST.MF</strong>中。</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// SpringBoot Plugin生效的非常关键的设置</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">bootJar {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  mainClass</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'com.example.application.MyApplication'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>最终构建结果中的META-INF/MANIFEST.MF</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>Manifest-Version: 1.0</span></span>
<span class="line"><span>Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx</span></span>
<span class="line"><span>Spring-Boot-Layers-Index: BOOT-INF/layers.idx</span></span>
<span class="line"><span>Start-Class: com.example.application.ApplicationFullApplication</span></span>
<span class="line"><span>Spring-Boot-Classes: BOOT-INF/classes/</span></span>
<span class="line"><span>Spring-Boot-Lib: BOOT-INF/lib/</span></span>
<span class="line"><span>Spring-Boot-Version: 2.4.5</span></span>
<span class="line"><span>Main-Class: org.springframework.boot.loader.JarLauncher</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h2 id="示例-多模块springboot项目" tabindex="-1">示例：多模块SpringBoot项目 <a class="header-anchor" href="#示例-多模块springboot项目" aria-label="Permalink to &quot;示例：多模块SpringBoot项目&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="项目结构" tabindex="-1">项目结构 <a class="header-anchor" href="#项目结构" aria-label="Permalink to &quot;项目结构&quot;">&ZeroWidthSpace;</a></h4>
<p><img src="//filecdn.code2life.top/gradle-structure.jpg" alt=""></p>
<h4 id="完整的gradle代码" tabindex="-1">完整的Gradle代码 <a class="header-anchor" href="#完整的gradle代码" aria-label="Permalink to &quot;完整的Gradle代码&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>./settings.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">rootProject</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'application'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">include </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">':application-api'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        ':application-boot'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        ':application-core'</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><strong>./build.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">buildscript {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ext {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        springBootVersion </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '2.4.5'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">plugins {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    id </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'java-library'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    id </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> version </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${springBootVersion}"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// java plugin内置的可设变量</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">group </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'com.example'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">version </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '1.0.0-SNAPSHOT'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sourceCompatibility </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> JavaVersion.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">VERSION_15</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">targetCompatibility </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> JavaVersion.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">VERSION_15</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">bootJar {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    mainClass</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'com.example.application.MyApplication'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">allprojects {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 目前Gradle版本不支持在allprojects下声明plugins，使用的是旧的写法</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    apply </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">plugin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'java-library'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    repositories {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        mavenCentral()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    configurations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">all {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        exclude </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'spring-boot-devtools'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        exclude </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'spring-boot-starter-tomcat'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        resolutionStrategy {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            cacheChangingModulesFor(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'seconds'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    test {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 要用junit可以换成：useJUnitPlatform()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        useTestNG()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        api platform(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.springframework.boot:spring-boot-dependencies:${springBootVersion}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 相当于provided scope，不打到构建产物中</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        compileOnly </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.projectlombok:lombok:1.18.20'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        annotationProcessor </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.projectlombok:lombok:1.18.20'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        testCompileOnly </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.projectlombok:lombok:1.18.20'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        testAnnotationProcessor </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.projectlombok:lombok:1.18.20'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 相当于test scope，仅在单元测试使用</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        testImplementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.testng:testng:7.4.0'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        testImplementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter-test'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 在allprojects下只要声明lombok，junit/testng这类通用依赖</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 不应该再添加任何属于某个子项目的依赖</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 根项目依赖 :application-boot 子项目</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 三个子项目的依赖关系是 boot -> core -> api</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 因此root project只要依赖boot项目</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation project(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">':application-boot'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br></div></div><p><strong>./release.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// maven_user / maven_password / GROUP_ID / VERSION / snapshots / releases 等变量需要写到gradle.properties和系统变量中</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">publishing {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    repositories {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        maven {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            url </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">VERSION</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">endsWith(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'-SNAPSHOT'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"snapshots"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"releases"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            credentials {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                username </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"maven_user"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ""</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> :</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"maven_user"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                password </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"maven_password"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ""</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> :</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> System.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getProperty(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"maven_password"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    publications {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        maven(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">MavenPublication</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            groupId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> GROUP_ID</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            artifactId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$project</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            version </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> VERSION</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            from components</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">java</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><p><strong>./application-api/build.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">plugins {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    id </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'maven-publish'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">apply </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"../release.gradle"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// api项目定义微服务之间调用的RPC/HTTP接口</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 这里留空，实际上要根据使用的RPC框架决定dependency</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p><strong>./application-core/build.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    api project(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":application-api"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 传递给boot项目</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    api </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    api </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter-web'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    api </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter-aop'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 仅在core项目中使用，不传递</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter-undertow'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'org.springframework.boot:spring-boot-starter-actuator'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'io.micrometer:micrometer-registry-prometheus:1.6.6'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'commons-configuration:commons-configuration:1.10'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p><strong>./application-boot/build.gradle</strong></p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dependencies {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    implementation project(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":application-core"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h2 id="参考" tabindex="-1">参考 <a class="header-anchor" href="#参考" aria-label="Permalink to &quot;参考&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="https://spring.io/blog/2021/03/18/spring-cloud-2020-0-2-aka-ilford-is-available" target="_blank" rel="noreferrer">https://spring.io/blog/2021/03/18/spring-cloud-2020-0-2-aka-ilford-is-available</a></li>
<li><a href="https://docs.gradle.org/current/userguide/userguide.html" target="_blank" rel="noreferrer">https://docs.gradle.org/current/userguide/userguide.html</a></li>
<li><a href="https://blog.gradle.org/optional-dependencies" target="_blank" rel="noreferrer">https://blog.gradle.org/optional-dependencies</a></li>
<li><a href="https://cloud.tencent.com/developer/article/1742859" target="_blank" rel="noreferrer">https://cloud.tencent.com/developer/article/1742859</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[毕业4年的程序猿，竟然去考研了]]></title>
            <link>https://code2life.top/blog/0059-post-graduate-exam</link>
            <guid>https://code2life.top/blog/0059-post-graduate-exam</guid>
            <pubDate>Fri, 01 Jan 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="毕业4年的程序猿-竟然去考研了" tabindex="-1">毕业4年的程序猿，竟然去考研了 <a class="header-anchor" href="#毕业4年的程序猿-竟然去考研了" aria-label="Permalink to &quot;毕业4年的程序猿，竟然去考研了&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="_12月26日发生了什么" tabindex="-1">12月26日发生了什么？ <a class="header-anchor" href="#_12月26日发生了什么" aria-label="Permalink to &quot;12月26日发生了什么？&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/exam7s.jpg" alt=""></p>
<p>周六早晨，<strong>6点50分</strong>，我挣扎起床，驱车来到不远不近的考点。</p>
<p><strong>7点40分</strong>，冬日的晨阳洒下一点点暖意，考点的门口，人头攒动，有三俩结伴，有人默默翻阅笔记，还有成排的考研机构广告牌旁边，发小册的“老师们”。</p>
<p><strong>8点15分</strong>，在考场里默默等待考试开始，一股无形的压力，落在稀疏的课桌和考生身上。</p>
<p>这间考场里的同学，只是今年377万考研学子的九牛一毛。</p>
<p>我喜欢发现身边的数据，一眼瞥过去，28个座位，约有七分之二的考生缺席，或许这个考场没什么代表性，亦或是报考在职MBA的“社会”人士，相比稚气未脱的毕业生们少了一些执着？</p>
<p><strong>11点30分</strong>，考完第一场《管理类联考综合能力》，离开考场的人们出奇的安静。管综的题量，对于大多数人是无法在三小时内做完的，我蒙了9道选择题，最后一分钟才草草写完作文。</p>
<p>木已成舟，我随着人群走出去，出去买了个杂粮饼，从排队开始等了7张饼。</p>
<p>考点门口，有5辆“杂粮饼三轮”，即使比平日价格贵一倍，个个生意也都不错。可惜煎饼师傅生产效率有限，许多人看到排队无望，就去别的地方了。做煎饼真是个不错的生意，说不定以后我也可以去试试，我一边嚼着饼一边胡思乱想着。</p>
<p><strong>12点30分</strong>，周围的人们都在准备下午的英语考试，大多在背诵作文，多年没有感受过这种氛围了。我闭目养神了一会，拿起册子快速翻阅，试图多复习几个生词和作文句子。直到1点30分后，再次拿起准考证进入考场，开始第二场考试，这是MBA初试的最后一场。</p>
<p><strong>下午4点30分</strong>，我和一些同学一样，提前交卷了。唉，要是英语的时间可以分给管综就好了，两门考试都是3个小时，但题量完全不是一个级别。</p>
<p><strong>5点10分</strong>，对结果的忧虑、考完的欣喜、准备考试的七十多天少陪了家人的愧疚... 种种心情，在开门见到家人笑脸的瞬间全部凝结，那一刻，一定是欣喜更多。</p>
<p><img src="//filecdn.code2life.top/exam-card.png" alt=""></p>
<h2 id="代码写的好好的-为什么去考研" tabindex="-1">代码写的好好的，为什么去考研？ <a class="header-anchor" href="#代码写的好好的-为什么去考研" aria-label="Permalink to &quot;代码写的好好的，为什么去考研？&quot;">&ZeroWidthSpace;</a></h2>
<p>上一次参加类似的考试，是8年前的高考。</p>
<p>大学本科毕业以后，作为一只程序猿，我一直从事一线开发工作，技术之路还有很长很长的路要走，我也挺喜欢做技术，并不想往管理方向发展。</p>
<p>那么，我为什么会做出这样的一个连自己都会惊讶的决定，去考在职MBA呢？</p>
<h3 id="在职mba是否值得考" tabindex="-1">在职MBA是否值得考 <a class="header-anchor" href="#在职mba是否值得考" aria-label="Permalink to &quot;在职MBA是否值得考&quot;">&ZeroWidthSpace;</a></h3>
<p>其实，我也不知道考在职MBA是否这是一个好的选择，这篇文章也<strong>不是</strong>推荐别人去考，只是分享心路历程罢了。</p>
<p>选择考研的人当中，考在职MBA的占比不多，其中大多数是为了走一条相对简单的路线，拿到一个更好的学历，在职场更具竞争力。</p>
<p>如果你有向管理层发展以及学历提升的需求，可以去了解一下，但这并不是我的目的。</p>
<p>我当前主线计划，仍然是纯技术方向发展，即使得到MBA学位，不会对我目前的职业发展有明显的作用。</p>
<p>我咨询过一些资深人士，其中一位老东家德高望重的前辈直接告诉我，“学这个没用”。我没有深问，大致能猜出可能的原因：或是学校学到的知识对实际工作作用有限，或是性价比过低，或是相比专硕或学硕分量轻了一些。</p>
<p>有的前辈说挺有用的，值得去试一试。</p>
<p>也有一些人非常“单纯”，就是想拿个MBA学位跳槽到另一个企业的管理层。这些完全以功利态度读MBA的人也存在，导致部分企业甚至有一点反感MBA，而他们自己即使达到目的，可能一辈子也就止步于此了。</p>
<h3 id="一次为-x计划-的投资" tabindex="-1">一次为“X计划”的投资 <a class="header-anchor" href="#一次为-x计划-的投资" aria-label="Permalink to &quot;一次为“X计划”的投资&quot;">&ZeroWidthSpace;</a></h3>
<p>或许每个人都有自己的人生“B计划”，我也一样，但我喜欢称之为<strong>X计划</strong>，它不是一条寻常路，充满未知与风险，也有一些理想主义 —— 在未来去做一个自己想做的产品，创造一个企业。</p>
<p>大学毕业之后，作为一线码农，在技术这条路有很明确的目标和清晰的路径。但在软件技术圈子的象牙塔之外，我什么都不懂。</p>
<p>几年的应用开发经历，也越发明白<strong>技术</strong>是做出<strong>大部分产品</strong>的<strong>关键因素</strong>，但不是<strong>决定性因素</strong>。</p>
<p>比如，创造一个在技术圈有影响力的开源项目，技术是决定性因素；而创造一个造福大众的产品和企业，光有技术是远远不够的。</p>
<p>要走出技术领域的舒适圈，就得弥补在其他领域知识和人脉的短板。</p>
<p><img src="//filecdn.code2life.top/transfer.jpg" alt=""></p>
<p>上面这张图不言而喻，理论上，只要后二者的收益总和，大于等于 同等时间投入到其他事情的收益，做这个转换就是不亏的。</p>
<p>这里有个悖论，时间与知识都是无价的，很难量化衡量某种转换是不是最优解。</p>
<p>一张看不见的贝叶斯网络在脑海中计算着，告诉我，此时此刻，应该继续沉淀，去了解更多的领域，去学习身边优秀的人，去等待能力匹配野心的时刻。</p>
<p>总之，从四处收集信息到分析并做出决定，前前后后花了几个小时。最终，我决定去像小学课文里过河的小马一样，自己亲身试一试。如果考过了去读科大的在职MBA，这也是毕业之后，第二次自己走出舒适圈。</p>
<h2 id="如何在两个半月的业余时间内备考" tabindex="-1">如何在两个半月的业余时间内备考？ <a class="header-anchor" href="#如何在两个半月的业余时间内备考" aria-label="Permalink to &quot;如何在两个半月的业余时间内备考？&quot;">&ZeroWidthSpace;</a></h2>
<p>从2020年10月初报名开始，满打满算也只有两个半月了，管综科目包括数学、逻辑、写作三个部分。</p>
<p>其中，数学基本都是忘的差不多的高中知识，逻辑题并不简单，备考时间非常紧张。我咨询了知乎上一位名为“<strong>王不留</strong>”的朋友，按照他的建议买了学习资料，国庆节后快递寄到，2020年10月9号，正式开始备考，<strong>仅剩77天</strong>。</p>
<p>我第一天列了计划：</p>
<ul>
<li><strong>10月</strong>粗略过一遍数学和逻辑，找到重难点；</li>
<li><strong>11月</strong>全部科目一刷完成，单词需要背完；</li>
<li><strong>12月</strong>后开始做模拟题并查漏补缺。</li>
</ul>
<p>每天学习完，记录当天复习的内容和进度。</p>
<p><img src="//filecdn.code2life.top/progress_exam.jpg" alt=""></p>
<h3 id="一次失败的备考计划" tabindex="-1">一次失败的备考计划 <a class="header-anchor" href="#一次失败的备考计划" aria-label="Permalink to &quot;一次失败的备考计划&quot;">&ZeroWidthSpace;</a></h3>
<p>可是计划赶不上变化，对原计划的修正也比较潦草，回顾起来，这个备考计划<strong>相当失败</strong>。</p>
<p>首先，我对每日备考的时长预测有误，计划每日能抽出3个小时以上学习，但工作日每天实际学习时间最多两个半小时，周末不超过6个小时。</p>
<p>其次，没考虑到学习效率的问题，早起45分钟，集中精力高效背单词的时间大约只有半个小时，下班后能集中精力高效备考的时间大约只有1.5个小时。</p>
<p>另外，我低估了大纲的知识量，数学的一些题目放在高中并不算简单，复杂的逻辑题，每道题需要花5分钟以上才能彻底想明白。</p>
<p>最终，我没能按计划备考，数学和逻辑仅完成了比较浅的一刷，单词勉强背完了大纲的90%。后期大部分时间在集中精力刷单词，10套真题卷仅做了4套。</p>
<h3 id="能否上岸" tabindex="-1">能否上岸 <a class="header-anchor" href="#能否上岸" aria-label="Permalink to &quot;能否上岸&quot;">&ZeroWidthSpace;</a></h3>
<p>现在回头反思一下，在77天的业余时间中，完成这么多知识点的复习，并且达到全都学会的水平的确是不现实的。</p>
<p>好在数学和逻辑还有些基础，大约70%的考纲单词还勉强认识，第一天自己裸考真题卷摸底，刚达国家线。不过好的大学刚达线是肯定不够的，一遍粗略的复习提升了一些，考完感觉差不多可以上岸。</p>
<p>今年报考的人数太多，如果初试被刷也没什么心理压力。<strong>毕竟，明年的考生还会更多，卷的更厉害。</strong></p>
<p>不管结果如何，已经尽力了。这两个半月，本职工作还有一些技术上的收获，但由于备考，知乎很少更新，2021年开始逐步整理分享出来吧。</p>
<h3 id="福利" tabindex="-1">福利 <a class="header-anchor" href="#福利" aria-label="Permalink to &quot;福利&quot;">&ZeroWidthSpace;</a></h3>
<p><img src="//filecdn.code2life.top/books-exam.jpg" alt=""></p>
<p>如果有读者想考MBA的话，我这些备考资料<strong>免费送一位朋友</strong>，大部分资料放在明年仍然是有用的，<strong>公众号或知乎评论区留言即可</strong>，人数大于等于2就抽奖吧。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——云原生12要素]]></title>
            <link>https://code2life.top/blog/0058-12-factor</link>
            <guid>https://code2life.top/blog/0058-12-factor</guid>
            <pubDate>Sat, 05 Sep 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——云原生12要素" tabindex="-1">软件设计杂谈——云原生12要素 <a class="header-anchor" href="#软件设计杂谈——云原生12要素" aria-label="Permalink to &quot;软件设计杂谈——云原生12要素&quot;">&ZeroWidthSpace;</a></h1>
<p>&quot;Twelve-Factor App&quot;的概念出现很久了，一般叫&quot;12要素&quot;，用来<strong>衡量一个后端服务是否适合搬到云上</strong>。以前不太明白其中的含义，经过一些实战之后，再回头看这些理论，发现这些“要素”个个一针见血，讲的正是实践中最容易踩的、最常见的坑。</p>
<h2 id="_12要素具体指什么" tabindex="-1">12要素具体指什么？ <a class="header-anchor" href="#_12要素具体指什么" aria-label="Permalink to &quot;12要素具体指什么？&quot;">&ZeroWidthSpace;</a></h2>
<p>&quot;Twelve-Factor App&quot;是Heroku创始人Adam Wiggins在2012年提出的（<a href="https://12factor.net/%EF%BC%89%EF%BC%8C%E6%8F%8F%E8%BF%B0%E4%BA%91%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%BA%94%E5%BD%93%E9%81%B5%E5%BE%AA%E7%9A%84%E4%B8%80%E4%BA%9B%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E3%80%82%E7%9B%B8%E6%AF%94%E4%BA%8E%E4%B8%8D%E7%AC%A6%E5%90%88%E8%BF%99%E4%BA%9B%E7%89%B9%E5%BE%81%E7%9A%84%E4%BC%A0%E7%BB%9F%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%EF%BC%8C%E5%85%B7%E6%9C%89%E8%BF%99%E4%BA%9B%E7%89%B9%E5%BE%81%E6%9B%B4%E5%90%88%E9%80%82%E4%BA%91%E5%8C%96%E3%80%82%E2%80%9CTwelve-Factor%E2%80%9D%E6%8C%87%E7%9A%84%E6%98%AF%E4%B8%8B%E9%9D%A2%E7%9A%8412%E6%9D%A1%EF%BC%88%E4%B8%8D%E6%98%AF%E4%B8%80%E4%B8%80%E5%AF%B9%E5%BA%94%E7%BF%BB%E8%AF%91%EF%BC%89%EF%BC%9A" target="_blank" rel="noreferrer">https://12factor.net/），描述云端服务应当遵循的一些最佳实践。相比于不符合这些特征的传统应用服务，具有这些特征更合适云化。“Twelve-Factor”指的是下面的12条（不是一一对应翻译）：</a></p>
<ol>
<li>Codebase：基线代码</li>
<li>Dependencies：显式和隔离的依赖</li>
<li>Configuration：配置分离存储到环境中</li>
<li>Backing services：分离基础的后端组件</li>
<li>Build, release, run：严格分离构建、发布、运行</li>
<li>Processes：无状态的服务进程</li>
<li>Port binding：自带端口绑定</li>
<li>Concurrency：通过进程的水平扩展增大并发能力</li>
<li>Disposability：易处置 - 快速启动和优雅退出</li>
<li>Dev/prod parity：环境对等</li>
<li>Log：日志作为事件流</li>
<li>Admin processes：分离管理类任务</li>
</ol>
<h2 id="如何理解这12点" tabindex="-1">如何理解这12点？ <a class="header-anchor" href="#如何理解这12点" aria-label="Permalink to &quot;如何理解这12点？&quot;">&ZeroWidthSpace;</a></h2>
<p>Adam是在Heroku这个Platform as a Service模式的企业积累了大量经验，总结出的这些“要素”。对于PaaS提供商，关注的是<strong>应用服务如何在其Platform上运行的更好</strong>，因此要理解这些要素，我们先得搞清楚一个服务是怎么在Platform上跑起来的，简化的流程如下图所示：</p>
<p><img src="//filecdn.code2life.top/release-process.png" alt=""></p>
<p>注：图片来自《Beyond the Twelve-Factor App》</p>
<p>落实到真实场景中具体是什么样的呢？Heroku国内用的很少，我们以标准的<strong>Kubernetes平台</strong>为例展开来看：<strong>一个典型的容器化的后端服务，从开发到上线需要经历哪些步骤</strong>。</p>
<ol>
<li>设计阶段：需求分析和领域设计、技术选型确定依赖的框架和组件、建立项目框架</li>
<li>开发阶段：开发、测试、代码评审，迭代到可发布的版本</li>
<li>创建镜像仓库，为服务编写Dockerfile，构建出服务的容器镜像</li>
<li>创建容器编排文件，确定非产线环境部署运维阶段的各项细节</li>
<li>在测试环境确认基础设施容量以及第三方组件，符合条件并初始化完毕，比如数据库的创建和初始化DDL的执行</li>
<li>准备部署到测试环境，在配置中心创建或更新配置文件，配置参数和密钥等</li>
<li>创建或更新持续集成、交付系统的任务管道， 执行CI/CD Pipeline，部署到测试环境</li>
<li>配置测试环境的访问入口，如反向代理的路由、域名等等</li>
<li>日志、监控、告警、链路追踪等相关组件接入</li>
<li>在测试环境进行完整的功能集成测试、性能测试</li>
<li>在预上线环境，重复步骤6-10</li>
<li>在产线环境，重复步骤6-10</li>
<li>继续迭代，完成开发和单测后，在每个环境重复步骤6-10，其中7,8,9中无需修改的部分可以跳过</li>
<li>每次迭代灰度发布，逐步放开新版本的流量</li>
</ol>
<p>可以看出，一个正经的后端系统，初次上线和后续迭代的流程已经比较复杂。如果单靠人力，单体系统勉强可以应付，毕竟单体系统即使变成&quot;大泥球&quot;，也大多处于人力可控的范围内。但随着<strong>复杂度进一步提升</strong>，整个系统演化成微服务系统，往往包含<strong>十几个、数十个、甚至成百上千个</strong>子服务，多个服务之间还有依赖关系，这其中的技术挑战是显而易见的。</p>
<p>复杂性无法避免，如何在复杂情况下，尽量提高效率、减少错误呢？</p>
<p>答案就是，在设计和开发阶段去<strong>迎合云平台以及整个生态的能力</strong>，从一开始就要<strong>做一个适合在云上跑的服务</strong>。</p>
<p>“12要素”应运而生，给了我们一把衡量“是否适合上云”的标尺。用个不太恰当的说法就是“屁股决定脑袋”。如果<strong>不遵循</strong>这些“要素”，不适应云平台提供的能力、不剥离业务无关的部分，随着<strong>服务规模增大、业务复杂度进一步提高</strong>，就非常容易引发问题了。</p>
<p>这也是“12要素”出现的背景和原因，了解这些之后，其内容就更好理解了。下面我们把12要素归为两类，一类是<strong>放之四海而皆准</strong>的，第二类是对<strong>云原生应用</strong>特别重要的，以<strong>举反例</strong>的形式逐一讲解。</p>
<h2 id="第一类-几乎任何场景都适合的" tabindex="-1">第一类：几乎任何场景都适合的 <a class="header-anchor" href="#第一类-几乎任何场景都适合的" aria-label="Permalink to &quot;第一类：几乎任何场景都适合的&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="基线代码-codebase" tabindex="-1">基线代码 - Codebase <a class="header-anchor" href="#基线代码-codebase" aria-label="Permalink to &quot;基线代码 - Codebase&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>One codebase tracked in revision control, many deploys.</p>
</blockquote>
<p>基线代码可以理解为多层意思：一个项目一个仓库；Git分支也不要分岔之后合不回来了；不要在多个仓库出现重复的代码，把通用的代码抽成独立维护的仓库。</p>
<p><strong>反模式的例子</strong>：多个不相干项目数百万行代码全部放到一个代码仓库；对于一些需求差异，直接复制项目仓库单独开发，同时维护多个仓库代码。想象一下这两个例子，CI/CD系统心理阴影面积多大。</p>
<p>另外，代码仓库的管理还影射了更深的含义。<strong>康威定律</strong>告诉我们，<strong>组织和团队的形态最终会反映到产品的形态上</strong>。因此看一个公司的代码仓库如何被创建和管理的，这个公司开发团队组织结构和技术管理水平也可见一斑。</p>
<h4 id="显式和隔离的依赖-dependencies" tabindex="-1">显式和隔离的依赖 - Dependencies <a class="header-anchor" href="#显式和隔离的依赖-dependencies" aria-label="Permalink to &quot;显式和隔离的依赖 - Dependencies&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Explicitly declare and isolate dependencies</p>
</blockquote>
<p>完善的依赖管理机制、显式的依赖声明文件和版本锁机制，能够减少因为错误的依赖版本导致的Bug。</p>
<p><strong>反模式的例子</strong>：Node.js之父Ryan Dahl另起炉灶创造了Deno，Deno的import远程代码就是Node世界的npm反向极端，造成了隐式依赖；Golang在1.13之前没有go module的时候，也是违反这条原则的。且不说不清晰的第三方依赖容易导致“投毒”，这对代码的问题定位、维护、交接都是很大的负担。</p>
<p>注：这一小节的“反模式”并不是指技术本身哪里不好，其创造者都是世界顶级的工程师和科学家，仅指它们的一些原生特性，对开发复杂的应用不够友好。</p>
<h4 id="配置分离存储到环境中-configuration" tabindex="-1">配置分离存储到环境中 - Configuration <a class="header-anchor" href="#配置分离存储到环境中-configuration" aria-label="Permalink to &quot;配置分离存储到环境中 - Configuration&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Store config in the environment</p>
</blockquote>
<p>配置数据和构建产物<strong>完全分离</strong>，配置数据<strong>单独管理</strong>，只在<strong>运行环境</strong>中出现。</p>
<p>《Beyond the Twelve-Factor App》中有一个比喻：<strong>代码制品、生产环境配置 是两个危险的化学物质，混合到一起就可能随时爆炸</strong>。因此，我们需要把配置（尤其是密钥类、功能开关、策略类配置）的重要性提升到很高的级别，小心翼翼地管理。</p>
<p>若是配置不完全和代码分离，相当于<strong>两个危险的化合物一开始就被混合在一起</strong>，部署的时候<strong>原地爆炸</strong>也不足为奇。</p>
<p><strong>反模式的例子</strong>：环境相关的配置，混在容器镜像、甚至代码包中，每个环境需要单独构建打包一个版本。这种“不正确”的做法在传统的开发模式中很常见。</p>
<h4 id="分离构建、发布、运行-build-release-run" tabindex="-1">分离构建、发布、运行 - Build, Release, Run <a class="header-anchor" href="#分离构建、发布、运行-build-release-run" aria-label="Permalink to &quot;分离构建、发布、运行 - Build, Release, Run&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Strictly separate build and run stages</p>
</blockquote>
<p>在本节一开始分享的图中有6个阶段：<strong>设计、开发、构建、发布、配置、运行</strong>。</p>
<p><strong>配置</strong>在上一小节已经说明为什么一定要分离出来。<strong>设计、开发</strong>在传统软件生命周期模型中已经是两步。</p>
<p>剩下的3个阶段就是：<strong>构建、发布、运行</strong>，而这三者在传统软件的发布流程中通常并没有完全分离。</p>
<p>为什么要强调“构建、发布、运行”三个阶段<strong>一定要</strong>分离开来呢？</p>
<p>有两个好处：</p>
<ul>
<li>职责和关注点的分离。<strong>构建</strong>是开发测试人员更关注的、<strong>发布</strong>是产品经理更关注的、<strong>运行</strong>是运维更关注的；</li>
<li><strong>流水线</strong>模式带来的效率提升，以及各阶段之间的缓冲空间，每个阶段有专门的工具和方法论。</li>
</ul>
<p>怎么做到这三个阶段的分离呢？流水线的运行不是靠人力保障的，自动化系统很重要。用好CI/CD系统、项目管理系统，制定好规则和流程并自动运转起来，足矣。</p>
<p><strong>反模式的例子</strong>：开发改完代码，本地打个Patch发给运维，也不告知产品经理改了什么，直接口头告诉运维批量更换某些文件。</p>
<h4 id="环境对等-dev-prod-parity" tabindex="-1">环境对等 - Dev/prod parity <a class="header-anchor" href="#环境对等-dev-prod-parity" aria-label="Permalink to &quot;环境对等 - Dev/prod parity&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Keep development, staging, and production as similar as possible</p>
</blockquote>
<p>开发、测试、预上线、生产环境等等，甚至本地环境，都保持环境一致，这样能最大限度减少“<strong>我本地是正常的啊”、“开发环境是正常的啊”、“是不是环境/机器问题</strong>”这类甩锅式的抱怨。</p>
<p><strong>反模式的例子</strong>：开发环境不容器化，产线容器化；开发环境用的MariaDB，产线用的MySQL；开发环境数据库没主从，产线配置了主从同步。MySQL读写分离时，主从同步那几毫秒的延迟导致各种奇怪Bug，在开发环境也许永远都重现不出来。</p>
<h2 id="第二类-对云原生应用及其重要的" tabindex="-1">第二类：对云原生应用及其重要的 <a class="header-anchor" href="#第二类-对云原生应用及其重要的" aria-label="Permalink to &quot;第二类：对云原生应用及其重要的&quot;">&ZeroWidthSpace;</a></h2>
<p>这篇题目为什么叫“云原生12要素&quot;呢？其实Adam的原义<strong>只是</strong>说<strong>云端应用</strong>应当具备的特征，并不是指<strong>云原生（Cloud Native）</strong>。本文想讲的不单是跑在云端的应用，而是有更现代化特征的<strong>云原生应用</strong>，所以用了这个题目。</p>
<p>其实在Adam提出“12要素”一年后的2013年，才出现“云原生”的概念，云原生的含义也经历了一些演变。</p>
<p>2015年Matt Stine在《Migrating to Cloud-Native Application Architectures》一书中对云原生的定义就是：</p>
<ul>
<li>符合12要素（The Twelve-Factor App）</li>
<li><strong>微服务</strong>（Microservices）</li>
<li>自助式敏捷基础设施（Self-Service Agile Infrastructure）</li>
<li>基于API协作（API-based Collaboration）</li>
<li>健壮、抗压能力（Anti-Fragility）</li>
</ul>
<p>后来云2015年Linux基金会发起原生基金组织（CNCF），给出的定义是：容器、自动化、<strong>微服务</strong>。</p>
<p>到了2017年，Pivotal稍微改了一点，变成了：DevOps、持续交付、<strong>微服务</strong>、容器。</p>
<p>目前为止，<a href="https://github.com/cncf/toc/blob/master/DEFINITION.md#%E4%B8%AD%E6%96%87%E7%89%88%E6%9C%AC" target="_blank" rel="noreferrer">CNCF官方最新的定义</a>在技术上的概括是：容器、服务网格、<strong>微服务</strong>、不可变基础设施和声明式API。</p>
<p>云原生含义的演化过程，有一个基调一直没变，就是微服务。微服务是<strong>当前</strong>云原生应用的<strong>表现形式</strong>，或许云原生以后还会进一步增加<strong>Serverless</strong>。下面这些“要素”，对微服务/无服务的设计和开发非常重要。</p>
<h4 id="分离基础的后端组件-backing-services" tabindex="-1">分离基础的后端组件 - Backing services <a class="header-anchor" href="#分离基础的后端组件-backing-services" aria-label="Permalink to &quot;分离基础的后端组件 - Backing services&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Treat backing services as attached resources</p>
</blockquote>
<p>所有依赖的基础组件或者其他应用服务，比如数据库、缓存服务、消息队列、二方/三方服务，都视为独立于自身之外的资源，独立部署，通过网络访问。</p>
<p>用面向对象的术语类比，就是<strong>视别的服务为“关联”的而非“组合</strong>的”。“关联”意味着更弱的耦合，仅通过<strong>网络端口</strong>与这些依赖的服务交互，而不是<strong>进程间通信</strong>。</p>
<p>“关联”模式像人与手机的关系，“组合”模式像人与人的大脑、四肢的关系。因此，“关联”模式还有一个好处，它会强迫我们去思考：如果依赖的服务宕机了怎么办？</p>
<p>就像很多人会有备用手机一样，大量的<strong>容错、降级</strong>处理的逻辑被写出来了，应用服务也获得了更强的鲁棒性。</p>
<p><strong>反模式的例子</strong>：把缓存服务和应用服务打包到同一个容器镜像，通过/var/redis.sock这样的Domain Socket形式访问；或者把第二方应用服务的源码直接复制到自己的代码中，在一个进程中互相调用。</p>
<h4 id="无状态的服务进程-processes" tabindex="-1">无状态的服务进程 - Processes <a class="header-anchor" href="#无状态的服务进程-processes" aria-label="Permalink to &quot;无状态的服务进程 - Processes&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Execute the app as one or more stateless processes</p>
</blockquote>
<p>按照上一节说的，把依赖的服务分离出去，一些应用服务已经可以实现“无状态”了。但有时候，还需要对应用内部做一些改造才能实现无状态。无状态是水平扩展的前提，对于Serverless应用更是必要条件。</p>
<p><strong>反模式的例子</strong>：应用服务的多个实例之间互相通信，共享一些内存数据；或者开发自治的集群选主、任务分发等功能。</p>
<h4 id="自带端口绑定-port-binding" tabindex="-1">自带端口绑定 - Port Binding <a class="header-anchor" href="#自带端口绑定-port-binding" aria-label="Permalink to &quot;自带端口绑定 - Port Binding&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Export services via port binding</p>
</blockquote>
<p>不要依赖运行平台提供端口绑定的功能，提供出去的可运行程序，直接运行就会绑定到某个端口。比如Springboot应用通常内嵌tomcat/undertow/jetty等Java Web容器，构建出的包直接运行就绑定了端口。</p>
<p><strong>反模式的例子</strong>：提供出去部署的包的是 放到Tomcat的war、放到IIS的dll，自己本身没有描述通信协议，也没有指定绑定的端口，完全依赖Tomcat/IIS的配置。</p>
<h4 id="通过进程的水平扩展增大并发能力-concurrency" tabindex="-1">通过进程的水平扩展增大并发能力 - Concurrency <a class="header-anchor" href="#通过进程的水平扩展增大并发能力-concurrency" aria-label="Permalink to &quot;通过进程的水平扩展增大并发能力 - Concurrency&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Scale out via the process model</p>
</blockquote>
<p>无状态的应用服务，很容易实现水平扩展，自身不会制约到并发能力。传统应用服务通常是性能靠提升单机配置，可用性靠双机热备；而云原生应用，注重的是伸缩能力（Elastic, Scalable）。</p>
<p><strong>反模式的例子</strong>：把Session放到内存中。</p>
<h4 id="易处置-快速启动和优雅退出-disposability" tabindex="-1">易处置：快速启动和优雅退出 - Disposability <a class="header-anchor" href="#易处置-快速启动和优雅退出-disposability" aria-label="Permalink to &quot;易处置：快速启动和优雅退出 - Disposability&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Maximize robustness with fast startup and graceful shutdown</p>
</blockquote>
<p>因为云原生应用需要保持更优秀的可伸缩性，服务的部署实例随时可能被创建出来、或者被销毁掉，这就要求服务自身提供快速启动和优雅退出能力。</p>
<p>不具有快速启动能力，水平扩容的速度受限；不具备优雅退出能力，缩容时未处理完的业务中断，会导致用户请求错误、数据不一致性等问题。</p>
<p><strong>反模式的例子</strong>：很重的Java服务启动耗时十几分钟；缩容靠kill -9强杀进程；服务也没有实现收到SIGTERM信号进入“跛脚鸭状态”，也没有等待请求处理完再关闭进程。</p>
<h4 id="日志作为事件流-logs" tabindex="-1">日志作为事件流 - Logs <a class="header-anchor" href="#日志作为事件流-logs" aria-label="Permalink to &quot;日志作为事件流 - Logs&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Treat logs as event streams</p>
</blockquote>
<p><strong>应用服务不应该管日志怎么处理</strong>，日志如何处理是平台的职责，而非应用自身的业务。因此，应用服务只要把日志作为事件流抛出去就好了，容器环境中，最好的办法就是直接打印到标准输出和标准错误（stdout, stderr）。</p>
<p><strong>反模式的例子</strong>：项目中写了一堆log4xx的复杂配置，日志文件存哪个路径、多长时间轮滚、保留多久删除。传统的软件这是必备的，但云原生应用，请仅保留打印到标准输出/标准错误。还有一个反模式的例子，在应用内就通过代码把日志抛到Kafka这类Broker中，无形中也让应用服务和Kafka耦合到了一起。</p>
<p>很多人不相信日志打印到stdout/stderr就完事了，是因为不够了解云原生世界中，各类日志收集和处理组件的强大。我们对传统的做法习以为常，却忘记了“单一职责原则”。</p>
<h4 id="分离管理类任务-admin-processes" tabindex="-1">分离管理类任务 - Admin Processes <a class="header-anchor" href="#分离管理类任务-admin-processes" aria-label="Permalink to &quot;分离管理类任务 - Admin Processes&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Run admin/management tasks as one-off processes</p>
</blockquote>
<p>什么是管理类任务（Admin Processes）？直译成“管理进程”感觉不太对，这里是Admin Processes指的是执行数据库DDL、周期执行的运维任务、一次性的数据迁移和修复等等这类事情，更贴切的说法是“后台管理任务”。</p>
<p><strong>反模式的例子</strong>：在应用服务运行环境中安装一个数据库客户端，运维人员手动跑一堆修改数据库的SQL；或者安装一些运维脚本，放到机器的cron table定期执行一些脚本。</p>
<p>这一条“要素”看似晦涩难懂，看反例就很清楚了。例子中的做法是传统模式经常干的事情，但这种模式显然不“Scalable”，用自动化流水线和统一的任务调度平台，而不是手动SSH到机器上靠人做。《Beyond the Twelve-Factor App》中传达了更激进的观念：压根不要出现这类一次性的（“One-Off”）任务，这类业务也视为后端服务，调度中心仅触发一个HTTP/RPC请求，调用服务的接口做这类业务。</p>
<p>举个正例帮助理解：如果要实现每天跑一次的数据分析脚本，除了到机器上加crontab这个最坏的办法，还有什么其他办法呢？</p>
<p>《Twelve-Factor App》告诉我们，可以用一次性的容器，每天创建一个容器执行脚本，确认执行成功后随即销毁，不成功可以自动重试，比如Kuernetes提供的CronJob机制。</p>
<p>《Beyond the Twelve-Factor App》告诉我们，可以在应用内或单独做一个服务，提供一个HTTP接口做这件事，调度平台每天触发的是HTTP调用，根据调用返回结果决定重试。彻底去除Admin Processes，所有的东西都是可伸缩的Backing Service。</p>
<h2 id="彩蛋-第三类" tabindex="-1">彩蛋：第三类 <a class="header-anchor" href="#彩蛋-第三类" aria-label="Permalink to &quot;彩蛋：第三类&quot;">&ZeroWidthSpace;</a></h2>
<p>“12要素”是“云原生应用”的必要条件，但并不能构成充分条件。</p>
<p>Kevin Hoffman在2016年写的《Beyond the Twelve-Factor App》<a href="https://www.oreilly.com/library/view/beyond-the-twelve-factor/9781492042631/" target="_blank" rel="noreferrer">一书</a>中，又重新修订了“12要素”，修改了一些描述，另外添加了<strong>API First、Telemetry、Authentication &amp; Authorization</strong>三个要素。前两个对微服务系统非常重要，第三个则是安全性的核心保障机制。</p>
<ul>
<li>API First：以<strong>API为中心的协作模式</strong>，永远先定义好API再做其他事情。微服务系统的开发模式，大多是多个小团队齐头并进，设计好之后，先定义API就非常重要了。以API作为团队、应用服务之间<strong>沟通的桥梁</strong>。</li>
<li>Telemetry：翻译成“遥测”有些别扭，属于可观测性（Observability）的一部分，上面说过的日志也属于可观测性的一部分。对于云原生系统，要<strong>杜绝</strong>传统的“SSH进去运行Debug工具”的事情发生，“遥测”是实现这一点的唯一手段。监控、告警、链路追踪，在微服务系统中缺一不可。</li>
<li>Authentication &amp; Authorization：认证和授权，属于安全性的要素，对传统的应用服务也适合。但云原生应用实现认证和授权的方式有所不同：对终端用户的认证授权往往在网关层就通过OAuth 2.0/OpenID Connect等协议统一处理了；对服务之间调用的认证授权通过Service Mesh可以做到零信任安全模式。</li>
</ul>
<p>这三个Kevin Hoffman新增的“要素”，本文篇幅有限就不展开了，有兴趣可以读原著了解细节。</p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>本文简述了<strong>云原生应用应具备的12+3个特征</strong>，主要受到《Twelve-Factor App》和《Beyond the Twelve-Factor App》的启发，也借鉴了其中的一些理论和案例。写这篇文章，不是“3分钟入门云原生理论”，而是较长时间实战后，思考总结的一点浅薄的个人理解。</p>
<p>实践归纳出理论，理论指导实践。我们在真正开发一个应用服务时，尽量按这些指导思想，可以做的更加“Cloud Native”一些，但也不能教条主义，把这些视为“金科玉律”。日志一定要用stdout打印吗？Admin Processes一定要独立执行吗？所有的服务都能实现“无状态”吗？</p>
<p>都不一定。</p>
<blockquote>
<p>Experience shows that compromise on purist ideals is as ever-present as death and taxes.</p>
</blockquote>
<p>“取舍”、“折衷”、“Trade-off”，一直是工程领域的关键词。没有最好的，只有适合的。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——依赖倒置]]></title>
            <link>https://code2life.top/blog/0057-dip</link>
            <guid>https://code2life.top/blog/0057-dip</guid>
            <pubDate>Thu, 20 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——依赖倒置" tabindex="-1">软件设计杂谈——依赖倒置 <a class="header-anchor" href="#软件设计杂谈——依赖倒置" aria-label="Permalink to &quot;软件设计杂谈——依赖倒置&quot;">&ZeroWidthSpace;</a></h1>
<p>昨天看到知乎一个问题问“<a href="https://www.zhihu.com/question/414620906/answer/1425162083" target="_blank" rel="noreferrer">JavaScript中如何使用依赖注入</a>”，正好最近在写软件设计杂谈系列，就顺便以这个问题为例把<strong>依赖倒置原则</strong>这个OOP理论中的重要原则讲一讲。</p>
<p>我们在Java Spring中经常听到&quot;依赖注入&quot;和&quot;控制反转&quot;两个术语，他们和&quot;依赖倒置原则&quot;是什么关系呢，这些术语是什么意思呢？</p>
<h2 id="到底什么是依赖注入-di-和控制反转-ioc" tabindex="-1">到底什么是依赖注入(DI)和控制反转(IoC)？ <a class="header-anchor" href="#到底什么是依赖注入-di-和控制反转-ioc" aria-label="Permalink to &quot;到底什么是依赖注入(DI)和控制反转(IoC)？&quot;">&ZeroWidthSpace;</a></h2>
<p>DI和IoC是实现<strong>依赖倒置原则</strong>的具体手段，依赖倒置是面向对象编程(OOP)的产物，一句话解释下依赖倒置原则：</p>
<blockquote>
<p>抽象不应该依赖实现，实现也不应该依赖实现，实现应该依赖抽象。</p>
</blockquote>
<p>乍一听很玄乎，什么是&quot;抽象&quot;、&quot;实现&quot;？</p>
<p>我们举个通俗的栗子。</p>
<p>假设你想去吃一碗牛肉面。</p>
<p>如果按照<strong>面向过程编程</strong>的思维，大概是这样的：</p>
<ul>
<li>输入：面粉、牛肉、辣椒酱 ；</li>
<li>制作牛肉面，你要按菜谱一步一步做；</li>
<li>输出：牛肉面。</li>
</ul>
<p>如果你不想自己做，那就按<strong>面向对象编程</strong>的思维，大概是这样的：</p>
<ul>
<li>你是一个Object，现在需要一碗牛肉面；</li>
<li>&quot;你&quot;需要一个依赖厨师Object，因为厨师有&quot;制作牛肉面&quot;这个方法，于是你雇了一个厨师；</li>
<li>你又调用了&quot;超市Object&quot;的购买方法，买到了 面粉、牛肉、辣椒酱；</li>
<li>跟厨师说你要的口味，把买到的食材给厨师，调用厨师Object的&quot;制作牛肉面&quot;方法，完成制作。</li>
</ul>
<p>等等，有没有发现哪里不对劲？</p>
<ul>
<li>我为了吃一碗牛肉面还要雇一个厨师？</li>
<li>我雇了厨师还要自己买食材？</li>
</ul>
<p>问题在于，&quot;我&quot;这个Object依赖了一个厨师Object，这个就叫&quot;<strong>实现&quot;依赖了&quot;实现</strong>&quot;。因为依赖了具体的&quot;实现&quot;，所以很多细节被暴露出来了，于是我试图把更多本不该我管的细节(买食材)传递给了具体的”实现&quot;(厨师)。</p>
<p>吃牛肉面的解决之道，<strong>不是雇一个厨师，而是去一个面馆</strong>，在面馆里看着菜单：&quot;一份微辣大份牛肉面，谢谢&quot;。</p>
<p>这里，&quot;菜单&quot;就是&quot;抽象&quot;，&quot;厨师Object&quot;就是&quot;实现&quot;。</p>
<p>&quot;我&quot;这个Object只需要依赖&quot;菜单&quot;提供的抽象接口，调用&quot;下单&quot;就能吃到牛肉面，而不关心背后的厨师是哪些，他们怎么买的食材，具体是怎么做出来的。这就叫&quot;<strong>实现应该依赖抽象</strong>&quot;。</p>
<p>如果&quot;我&quot;这个Object如果依赖了厨师Object，调用了 <strong>new Cook()</strong>，就必然要管理这个厨师从初始化到解雇的整个流程了。</p>
<p>也就是说当我调用 new 的瞬间之后：对象完整的生命周期、资源如何创建和销毁全都要我去管了。</p>
<p>但实际上按照下馆子的方式，厨师是餐馆管理的，这一点非常关键：</p>
<ul>
<li><strong>餐馆</strong>就是那个<strong>控制反转(IoC)容器</strong>，总要有一个东西来管理这些抽象的具体实现，比如餐馆对内管理了数十个不同的厨师，对外提供10个菜品。</li>
<li>餐馆给&quot;我&quot;这个Object&quot;注入&quot;菜单的过程，就是<strong>依赖注入(DI)</strong></li>
<li>我应该依赖 抽象的&quot;菜单&quot; 去下单，而不是试图把食材递给厨师张三看着他做，这就是<strong>依赖倒置原则</strong>。</li>
</ul>
<p>对比 面向过程、初级面向对象、符合依赖倒置原则的面向对象 这三个方式，我们发现事情似乎变简单了，我不用自己买食材做面条，直接下馆子就OK了，这就是面向对象编程的<strong>封装</strong>和<strong>信息隐藏</strong>的力量。做牛肉面的复杂度并没有被降低，但整个流程和&quot;我&quot;这个Object的耦合解开了。</p>
<p>再回到之前对依赖倒置原则的解释:</p>
<blockquote>
<p>抽象不应该依赖实现，实现也不应该依赖实现，实现应该依赖抽象。</p>
</blockquote>
<p>我们换成 厨师 菜单 客人:</p>
<blockquote>
<p>菜单不应该依赖厨师，客人也不应该依赖厨师，客人应该依赖菜单。</p>
</blockquote>
<p>一下子清晰多了。</p>
<p>我这里刻意避开类(Class)这个概念，是为了说明OOP的思维并不一定要&quot;类&quot;这个概念，重点在于通过信息隐藏来解耦，让复杂的软件系统可以分而治之。</p>
<h2 id="java-spring中的di和ioc" tabindex="-1">Java Spring中的DI和IoC <a class="header-anchor" href="#java-spring中的di和ioc" aria-label="Permalink to &quot;Java Spring中的DI和IoC&quot;">&ZeroWidthSpace;</a></h2>
<p>Spring框架提供了<strong>XML</strong>和<strong>Java Config注解</strong>两种方式来告诉Spring这个IoC容器，需要管理哪些抽象接口的具体实现。如今XML方式几乎没有多少人用了，注解声明一个Class是@Bean @Component @Service @Controller @Repository等等这些的时候，Spring就把这个类初始化一个单例出来，管理整个声明周期，提供了一些诸如 @PostConstruct @PreDestroy等钩子用来定制Bean。依赖方直接通过 @Resource @Autowired 等注解，或者直接构造器声明，就能拿到一个Bean的具体实现了。</p>
<p>通常这些Bean是作为Interface类型的，这样方便扩展不同的Implementation，用@Qualified或按名称注入依赖，可以选择不同的实现。</p>
<p>Spring这个IoC容器管理Bean的生命周期流程，参考下面这张图：</p>
<p><img src="//filecdn.code2life.top/spring-bean.png" alt=""></p>
<h2 id="如何在javascript中使用ioc" tabindex="-1">如何在JavaScript中使用IoC? <a class="header-anchor" href="#如何在javascript中使用ioc" aria-label="Permalink to &quot;如何在JavaScript中使用IoC?&quot;">&ZeroWidthSpace;</a></h2>
<p>其实主流的几个组件化MVVM框架，Angular，Vue，React，就已经用了依赖注入了，框架本身就是IoC容器。</p>
<p>不知庐山真面目，只缘身在此山中。</p>
<p>以Vue为例：</p>
<ul>
<li>我们在组件中用&quot;<strong>components</strong>&quot;声明依赖的组件时，也是一种依赖注入。也许有人说，注入的明明是具体的组件&quot;实现&quot;而不是&quot;抽象&quot;啊？ 组件B依赖组件A，但在组件B中根本没有去 new 组件A，也没有管A什么时候创建，什么时候销毁，需要怎么初始化，只是为了告诉Vue这个IoC容器：组件B依赖组件A这个事情，组件的A的 <strong>init compile mount destroy</strong> 这些具体的流程和实现的管理不需要B去关心，因此这个声明可以看做是依赖了A的&quot;抽象&quot;。这里的&quot;抽象&quot;并不一定是类似Java的&quot;Interface&quot;这种形式。</li>
<li>控制反转(IoC)容器，就是统一管理各个实现如何初始化、从生到死整个过程的超级管家，Vue框架本身就干了这个事情，当你用<strong>Vue.component， Vue.use</strong>把组件注册到Vue里面的时候，这个组件的实例什么时候挂载到什么地方，都可以看作由Vue这个IoC容器来控制的。</li>
<li>上面说Vue的父子组件之间直接声明components是一种依赖注入，还有一个更明显的 <strong>inject provide</strong> 直接给所有后代组件都注入依赖。同样，inject/provide注入给子孙后代组件，这些后代也不用管祖先组件是怎么创建和销毁的。</li>
</ul>
<p>Angular从1.x的AngularJS，在参数中直接传递依赖组件的字符串，到后来新的Angular框架，都具有非常明显的IoC和DI的特征。而require.js这类工具解决的不是对象与对象之间的耦合问题，所以不完全算依赖注入和控制反转。</p>
<p>另一个非前端的例子，Node.js服务端框架 nest.js，和Java Spring以及Angular的用法非常类似，可以阅读官方文档，也有对IoC和DI的解释和具体使用示例，讲的非常详尽。</p>
<p><a href="https://docs.nestjs.com/" target="_blank" rel="noreferrer">https://docs.nestjs.com/</a>
​
因此，如果项目相对复杂，开始用这些前后端框架，构造器代码中很少 new 非DTO/VO/PO对象出来的时候，就已经在欢快地使用依赖注入了，而IoC容器就是那个为你管理这些具体实现对象的生与死的幕后Boss。</p>
<h2 id="依赖注入的问题和局限性" tabindex="-1">依赖注入的问题和局限性 <a class="header-anchor" href="#依赖注入的问题和局限性" aria-label="Permalink to &quot;依赖注入的问题和局限性&quot;">&ZeroWidthSpace;</a></h2>
<p>依赖注入一定是&quot;好的模式&quot;吗？</p>
<p>不完全是。今天我去餐馆说要一份不辣的牛肉面，结果上来一份巨辣无比的牛肉面。这就是&quot;信息隐藏&quot;的代价。在Java中，SpringBoot已经把IoC和DI发展的淋漓尽致了，一个@EnableAutoConfiguration注解，背后做了很多黑箱的事情，各种约定式的配置直接告诉Spring容器该做什么事情，甚至无需写一行代码。物极必反，这样反而让项目容易出现过多冗余的依赖、大量被Spring容器中的Bean在背后难以控制、一个接口存在过多的实现类、不确定的互相影响、依赖加载顺序问题等等。</p>
<p>虽然可能存在这些问题，但我觉得在以面向对象编程为主的复杂系统引入IoC容器和DI仍然是有必要的，上述这些问题也有办法避免或解决。让对象自己管理所依赖对象的生命周期，就像直接雇一个厨师来做牛肉面一样简单粗暴，但更容易违背<strong>迪米特法则</strong>等其他OOP的理念，项目的<strong>可扩展性</strong>和<strong>可维护性</strong>会受到更强的制约。这里前提是OOP情况下的建议，当然OOP也有一些局限性，不一定非要用OOP作为编程范式。</p>
<p>另一个场景，如果只是一些简单的页面或服务，没有复杂的组件/服务之间的交互，是没有必要为了用DI而用DI的。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>依赖注入(DI)<strong>和</strong>控制反转(IoC)<strong>是具体的手段，是OOP理论中</strong>依赖倒置原则</strong>的体现形式，通过<strong>信息隐藏</strong>来降低对象之间的<strong>耦合</strong>，这就是依赖倒置解决的问题。这种思想的运用不限于语言和框架。像Java Spring用工厂/模板方法/代理/单例模式、、注解、反射、动态代理这一系列设计模式和相关技术实现了IoC容器，而在没有类似Spring的语言和框架中运用这一思想的时候，无需实现如此复杂的框架，只要达到依赖倒置的&quot;实现&quot;和&quot;实现&quot;的解耦效果即可。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——性能优化的十种手段（上篇）]]></title>
            <link>https://code2life.top/blog/0055-performance</link>
            <guid>https://code2life.top/blog/0055-performance</guid>
            <pubDate>Sat, 15 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——性能优化的十种手段-上篇" tabindex="-1">软件设计杂谈——性能优化的十种手段（上篇） <a class="header-anchor" href="#软件设计杂谈——性能优化的十种手段-上篇" aria-label="Permalink to &quot;软件设计杂谈——性能优化的十种手段（上篇）&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="索引" tabindex="-1">索引 <a class="header-anchor" href="#索引" aria-label="Permalink to &quot;索引&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="/blog/0055-performance.html">软件设计杂谈——性能优化的十种手段（上篇）</a></li>
<li><a href="/blog/0056-performance2.html">软件设计杂谈——性能优化的十种手段（中篇）</a></li>
<li><a href="/blog/0056-performance3.html">软件设计杂谈——性能优化的十种手段（下篇）</a></li>
</ul>
<h2 id="引言-取与舍" tabindex="-1">引言：取与舍 <a class="header-anchor" href="#引言-取与舍" aria-label="Permalink to &quot;引言：取与舍&quot;">&ZeroWidthSpace;</a></h2>
<p>软件设计开发某种意义上是“取”与“舍”的艺术。关于性能方面，就像建筑设计成抗震9度需要额外的成本一样，高性能软件系统也意味着更高的实现成本，有时候与其他质量属性甚至会冲突，比如安全性、可扩展性、可观测性等等。大部分时候我们需要的是：在业务遇到瓶颈之前，利用常见的技术手段将系统优化到预期水平。那么，<strong>性能优化有哪些技术方向和手段呢</strong>？</p>
<p><strong>性能优化通常是“时间”与“空间”的互换与取舍</strong>。本篇分两个部分，在上篇，讲解六种通用的“时间”与“空间”互换取舍的手段：</p>
<ul>
<li>索引术</li>
<li>压缩术</li>
<li>缓存术</li>
<li>预取术</li>
<li>削峰填谷术</li>
<li>批量处理术</li>
</ul>
<p>在下篇，介绍四种进阶性的内容，大多与<strong>提升并行能力</strong>有关：</p>
<ul>
<li>八门遁甲 —— 榨干计算资源</li>
<li>影分身术 —— 水平扩容</li>
<li>奥义 —— 分片术</li>
<li>秘术 —— 无锁术</li>
</ul>
<p>每种性能优化的技术手段，我都找了一张<strong>应景</strong>的《火影忍者》中人物或忍术的配图，评论区答出任意人物或忍术送一颗小星星。</p>
<p>（注：所有配图来自动漫《火影忍者》，部分图片添加了文字方便理解，仅作技术交流用途）</p>
<h2 id="索引术" tabindex="-1">索引术 <a class="header-anchor" href="#索引术" aria-label="Permalink to &quot;索引术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/index-dog.png" alt=""></p>
<p>10ms之后。</p>
<p><img src="https://filecdn.code2life.top/perf/index-dog-park.png" alt=""></p>
<p><strong>索引</strong>的原理是<strong>拿额外的存储空间换取查询时间</strong>，增加了<strong>写入数据</strong>的开销，但使<strong>读取数据</strong>的时间复杂度一般从O(n)降低到O(logn)甚至O(1)。索引不仅在数据库中广泛使用，前后端的开发中也在不知不觉运用。</p>
<p>在数据集比较大时，不用索引就像从一本<strong>没有目录而且内容乱序</strong>的新华字典查一个字，得一页一页全翻一遍才能找到；用索引之后，就像用拼音先在目录中先<strong>找到要查到字在哪一页</strong>，直接翻过去就行了。书籍的目录是典型的树状结构，那么软件世界常见的索引有哪些数据结构，分别在什么场景使用呢？</p>
<ul>
<li><strong>哈希表（Hash Table）</strong>：哈希表的原理可以类比银行办业务取号，给每个人一个号（计算出的Hash值），叫某个号直接对应了某个人，索引效率是最高的O(1)，消耗的存储空间也相对更大。K-V存储组件以及各种编程语言提供的Map/Dict等数据结构，多数底层实现是用的哈希表。</li>
<li><strong>二叉搜索树（Binary Search Tree）</strong>：有序存储的二叉树结构，在编程语言中广泛使用的<a href="https://www.jianshu.com/p/e136ec79235c" target="_blank" rel="noreferrer"><strong>红黑树</strong></a>属于二叉搜索树，确切的说是“不完全平衡的”二叉搜索树。从C++、Java的TreeSet、TreeMap，到<a href="https://en.wikipedia.org/wiki/Completely_Fair_Scheduler" target="_blank" rel="noreferrer">Linux的CPU调度</a>，都能看到红黑树的影子。Java的HashMap在发现某个Hash槽的链表长度大于8时也会将链表升级为红黑树，而相比于红黑树“更加平衡”的AVL树反而实际用的更少。</li>
<li><strong>平衡多路搜索树（B-Tree）</strong>：这里的B指的是Balance而不是Binary，二叉树在大量数据场景会导致查找深度很深，解决办法就是变成多叉树，MongoDB的索引用的就是B-Tree。</li>
<li><strong>叶节点相连的平衡多路搜索树（B+ Tree）</strong>：B+ Tree是B-Tree的变体，只有叶子节点存数据，叶子与相邻叶子相连，MySQL的索引用的就是B+树，Linux的一些文件系统也使用的B+树索引inode。其实B+树还有一种在枝桠上再加链表的变体：B*树，暂时没想到实际应用。</li>
<li><strong>日志结构合并树（LSM Tree）</strong>：Log Structured Merge Tree，简单理解就是像日志一样顺序写下去，多层多块的结构，上层写满压缩合并到下层。LSM Tree其实本身是为了优化写性能牺牲读性能的数据结构，并不能算是索引，但在大数据存储和一些NoSQL数据库中用的很广泛，因此这里也列进去了。</li>
<li><strong>字典树（Trie Tree）</strong>：又叫前缀树，从树根串到树叶就是数据本身，因此树根到枝桠就是前缀，枝桠下面的所有数据都是匹配该前缀的。这种结构能非常方便的做前缀查找或词频统计，典型的应用有：自动补全、URL路由。其变体基数树（Radix Tree）在Nginx的Geo模块处理子网掩码前缀用了；Redis的Stream、Cluster等功能的实现也用到了基数树（Redis中叫Rax）。</li>
<li><strong>跳表（Skip List）</strong>：是一种多层结构的有序链表，插入一个值时有一定概率“晋升”到上层形成间接的索引。跳表更适合大量并发写的场景，不存在红黑树的再平衡问题，Redis强大的ZSet底层数据结构就是哈希加跳表。</li>
<li><strong>倒排索引（Inverted index）</strong>：这样翻译不太直观，可以叫“关键词索引”，比如书籍末页列出的术语表就是倒排索引，标识出了每个术语出现在哪些页，这样我们要查某个术语在哪用的，从术语表一查，翻到所在的页数即可。倒排索引在全文索引存储中经常用到，比如ElasticSearch非常核心的机制就是倒排索引；Prometheus的时序数据库按标签查询也是在用倒排索引。</li>
</ul>
<p><strong>数据库主键之争</strong>：自增长 vs UUID。主键是很多数据库非常重要的索引，尤其是MySQL这样的RDBMS会经常面临这个难题：是用自增长的ID还是随机的UUID做主键？</p>
<p>自增长ID的性能最高，但不好做分库分表后的全局唯一ID，自增长的规律可能泄露业务信息；而UUID不具有可读性且太占存储空间。争执的结果就是找一个兼具二者的优点的<strong>折衷方案</strong>：用<strong>雪花算法</strong>生成分布式环境全局唯一的ID作为业务表主键，性能尚可、不那么占存储、又能保证全局单调递增，但引入了额外的复杂性，再次体现了取舍之道。</p>
<p>再回到<strong>数据库</strong>中的索引，建索引要注意哪些点呢？</p>
<ul>
<li>定义好主键并尽量使用主键，多数数据库中，主键是效率最高的<strong>聚簇索引</strong>；</li>
<li>在<strong>Where</strong>或<strong>Group By、Order By、Join On</strong>条件中用到的字段也要<strong>按需</strong>建索引或联合索引，MySQL中搭配explain命令可以查询DML是否利用了索引；</li>
<li>类似枚举值这样重复度太高的字段<strong>不适合</strong>建索引（如果有位图索引可以建），频繁更新的列不太适合建索引；</li>
<li>单列索引可以根据<strong>实际查询的字段</strong>升级为<strong>联合索引</strong>，通过部分冗余达到<strong>索引覆盖</strong>，以<strong>避免回表</strong>的开销；</li>
<li>尽量减少索引冗余，比如建A、B、C三个字段的联合索引，Where条件查询A、A and B、A and B and C 都可以利用该联合索引，就无需再给A单独建索引了；</li>
<li>根据数据库特有的索引特性选择适合的方案，比如像MongoDB，还可以建自动删除数据的<strong>TTL索引</strong>、不索引空值的<strong>稀疏索引</strong>、地理位置信息的<strong>Geo索引</strong>等等。</li>
</ul>
<p><strong>数据库之外</strong>，在代码中也能应用索引的思维，比如对于集合中大量数据的查找，使用<strong>Set、Map、Tree</strong>这样的数据结构，其实也是在用哈希索引或树状索引，比<strong>直接遍历</strong>列表或数组查找的性能高很多。</p>
<h2 id="缓存术" tabindex="-1">缓存术 <a class="header-anchor" href="#缓存术" aria-label="Permalink to &quot;缓存术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/cache-l1.png" alt=""></p>
<p><strong>缓存</strong>优化性能的原理和索引一样，是拿额外的<strong>存储空间换取查询时间</strong>。缓存无处不在，设想一下我们在浏览器打开这篇文章，会有多少层缓存呢？</p>
<ul>
<li>首先解析DNS时，浏览器一层DNS缓存、操作系统一层DNS缓存、DNS服务器链上层层缓存；</li>
<li>发送一个GET请求这篇文章，服务端很可能早已将其缓存在KV存储组件中了；</li>
<li>即使没有击中缓存，数据库服务器内存中也缓存了最近查询的数据；</li>
<li>即使没有击中数据库服务器的缓存，数据库从索引文件中读取，操作系统已经把热点文件的内容放置在Page Cache中了；</li>
<li>即使没有击中操作系统的文件缓存，直接读取文件，大部分固态硬盘或者磁盘本身也自带缓存；</li>
<li>数据取到之后服务器用模板引擎渲染出HTML，模板引擎早已解析好缓存在服务端内存中了；</li>
<li>历经数十毫秒之后，终于服务器返回了一个渲染后的HTML，浏览器端解析DOM树，发送请求来加载静态资源；</li>
<li>需要加载的静态资源可能因Cache-Control在浏览器本地磁盘和内存中已经缓存了；</li>
<li>即使本地缓存到期，也可能因Etag没变服务器告诉浏览器304 Not Modified继续缓存；</li>
<li>即使Etag变了，静态资源服务器也因其他用户访问过早已将文件缓存在内存中了；</li>
<li>加载的JS文件会丢到JS引擎执行，其中可能涉及的种种缓存就不再展开了；</li>
<li>整个过程中链条上涉及的<strong>所有的计算机和网络设备</strong>，执行的热点代码和数据很可能会载入CPU的多级高速缓存。</li>
</ul>
<p>这里列举的<strong>仅仅是一部分</strong>常见的缓存，就有多种多样的形式：从廉价的磁盘到昂贵的CPU高速缓存，最终目的都是用来换取宝贵的时间。</p>
<p><strong>缓存是“银弹”吗？</strong></p>
<p>不，Phil Karlton 曾说过：</p>
<blockquote>
<p>计算机科学中只有两件困难的事情：缓存失效和命名规范。
There are only two hard things in Computer Science: cache invalidation and naming things.</p>
</blockquote>
<p>缓存的使用除了带来额外的复杂度以外，还面临如何处理<strong>缓存失效</strong>的问题。</p>
<ul>
<li>多线程并发编程需要用各种手段（比如Java中的synchronized volatile）防止并发更新数据，一部分原因就是防止线程<strong>本地缓存的不一致</strong>；</li>
<li>缓存失效衍生的问题还有：<strong>缓存穿透、缓存击穿、缓存雪崩</strong>。解决用不存在的Key来穿透攻击，需要用空值缓存或布隆过滤器；解决单个缓存过期后，瞬间被大量恶意查询击穿的问题需要做查询互斥；解决某个时间点大量缓存同时过期的雪崩问题需要添加随机TTL；</li>
<li>热点数据如果是<strong>多级缓存</strong>，在发生修改时需要清除或修改<strong>各级缓存</strong>，这些操作往往不是原子操作，又会涉及各种不一致问题。</li>
</ul>
<p>除了通常意义上的缓存外，<strong>对象重用的池化技术</strong>，也可以看作是一种<strong>缓存的变体</strong>。常见的诸如JVM，V8这类运行时的<strong>常量池、数据库连接池、HTTP连接池、线程池、Golang的sync.Pool对象池</strong>等等。在需要某个资源时从现有的池子里直接拿一个，稍作修改或直接用于另外的用途，池化重用也是性能优化常见手段。</p>
<h2 id="压缩术" tabindex="-1">压缩术 <a class="header-anchor" href="#压缩术" aria-label="Permalink to &quot;压缩术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/compact.png" alt=""></p>
<p>说完了两个“空间换时间”的，我们再看一个“<strong>时间换空间</strong>”的办法——<strong>压缩</strong>。压缩的原理<strong>消耗计算的时间，换一种更紧凑的编码方式来表示数据</strong>。</p>
<p>为什么要拿时间换空间？时间不是最宝贵的资源吗？</p>
<p>举一个视频网站的例子，如果不对视频做任何压缩编码，因为带宽有限，巨大的数据量在网络传输的耗时会比编码压缩的耗时多得多。<strong>对数据的压缩虽然消耗了时间来换取更小的空间存储，但更小的存储空间会在另一个维度带来更大的时间收益</strong>。</p>
<p>这个例子本质上是：“<strong>操作系统内核与网络设备处理负担 vs 压缩解压的CPU/GPU负担</strong>”的权衡和取舍。</p>
<p>我们在代码中通常用的是<strong>无损压缩</strong>，比如下面这些场景:</p>
<ul>
<li>HTTP协议中Accept-Encoding添加Gzip/deflate，服务端对接受压缩的文本（JS/CSS/HTML）请求做压缩，大部分图片格式本身已经是压缩的无需压缩；</li>
<li>HTTP2协议的头部HPACK压缩；</li>
<li>JS/CSS文件的混淆和压缩（Uglify/Minify）；</li>
<li>一些RPC协议和消息队列传输的消息中，采用二进制编码和压缩（Gzip、Snappy、LZ4等等）；</li>
<li>缓存服务存过大的数据，通常也会事先压缩一下再存，取的时候解压；</li>
<li>一些大文件的存储，或者不常用的历史数据存储，采用更高压缩比的算法存储；</li>
<li>JVM的对象指针压缩，JVM在32G以下的堆内存情况下默认开启“UseCompressedOops”，用4个byte就可以表示一个对象的指针，这也是JVM尽量不要把堆内存设置到32G以上的原因；</li>
<li>MongoDB的二进制存储的BSON相对于纯文本的JSON也是一种压缩，或者说更紧凑的编码。但更紧凑的编码也意味着更差的可读性，这一点也是需要取舍的。纯文本的JSON比二进制编码要更占存储空间但却是REST API的主流，因为数据交换的场景下的可读性是非常重要的。</li>
</ul>
<p><strong>信息论</strong>告诉我们，无损压缩的极限是<a href="//www.ruanyifeng.com/blog/2014/09/information-entropy.html" target="_blank" rel="noreferrer"><strong>信息熵</strong></a>。进一步减小体积只能以损失部分信息为代价，也就是<strong>有损压缩</strong>。</p>
<p><img src="//filecdn.code2life.top/entropy.png" alt=""></p>
<p><strong>那么，有损压缩有哪些应用呢？</strong></p>
<ul>
<li>预览和缩略图，低速网络下视频降帧、降清晰度，都是对信息的有损压缩；</li>
<li>音视频等多媒体数据的<strong>采样和编码</strong>大多是有损的，比如MP3是利用傅里叶变换，有损地存储音频文件；jpeg等图片编码也是有损的。虽然有像WAV/PCM这类无损的音频编码方式，但多媒体数据的<strong>采样本身就是有损的</strong>，相当于只截取了真实世界的极小一部分数据；</li>
<li><strong>散列化</strong>，比如K-V存储时Key过长，先对Key执行一次“傻”系列（SHA-1、SHA-256）哈希算法变成固定长度的短Key。另外，散列化在文件和数据验证（MD5、CRC、HMAC）场景用的也非常多，无需耗费大量算力对比完整的数据。</li>
</ul>
<p>除了有损/无损压缩，但还有一个办法，就是<strong>压缩的极端</strong>——从根本上<strong>减少数据或彻底删除</strong>。</p>
<p><strong>能减少的就减少</strong>：</p>
<ul>
<li>JS打包过程“摇树”，去掉没有使用的文件、函数、变量；</li>
<li>开启HTTP/2和高版本的TLS，减少了Round Trip，节省了TCP连接，自带大量性能优化；</li>
<li>减少不必要的信息，比如Cookie的数量，去掉不必要的HTTP请求头；</li>
<li>更新采用增量更新，比如HTTP的PATCH，只传输变化的属性而不是整条数据；</li>
<li>缩短单行日志的长度、缩短URL、在具有可读性情况下用短的属性名等等；</li>
<li>使用位图和位操作，用风骚的<strong>位操作最小化存取的数据</strong>。典型的例子有：用Redis的位图来记录统计海量用户登录状态；布隆过滤器用位图排除不可能存在的数据；大量开关型的设置的存储等等。</li>
</ul>
<p><strong>能删除的就删除</strong>：</p>
<ul>
<li>删掉不用的数据；</li>
<li>删掉不用的索引；</li>
<li>删掉不该打的日志；</li>
<li>删掉不必要的通信代码，不去发不必要的HTTP、RPC请求或调用，轮询改发布订阅；</li>
<li><strong>终极方案：砍掉整个功能</strong>。</li>
</ul>
<blockquote>
<p>No code is the best way to write secure and reliable applications. Write nothing; deploy nowhere. —— Kelsey Hightower</p>
</blockquote>
<h2 id="预取术" tabindex="-1">预取术 <a class="header-anchor" href="#预取术" aria-label="Permalink to &quot;预取术&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>预取</strong>通常搭配缓存一起用，其原理是<strong>在缓存空间换时间基础上</strong>更进一步，再加上一次“<strong>时间换时间</strong>”，也就是：<strong>用事先预取的耗时，换取第一次加载的时间</strong>。当可以猜测出以后的某个时间很有可能会用到某种数据时，把数据预先取到需要用的地方，能大幅度提升用户体验或服务端响应速度。</p>
<p><img src="//filecdn.code2life.top/perf/preloading.png" alt=""></p>
<p>是否用预取模式就像自助餐餐厅与厨师现做的区别，在自助餐餐厅可以直接拿做好的菜品，一般餐厅需要坐下来等菜品现做。那么，预取在哪些实际场景会用呢？</p>
<ul>
<li>视频或直播类网站，在播放前先缓冲一小段时间，就是预取数据。有的在播放时不仅预取这一条数据，甚至还会预测下一个要看的其他内容，提前把数据取到本地；</li>
<li><strong>HTTP/2 Server Push</strong>，在浏览器请求某个资源时，服务器顺带把其他相关的资源一起推回去，HTML/JS/CSS几乎同时到达浏览器端，相当于浏览器被动预取了资源；</li>
<li>一些客户端软件会用常驻进程的形式，提前预取数据或执行一些代码，这样可以极大提高第一次使用的打开速度；</li>
<li>服务端同样也会用一些预热机制，一方面<strong>热点数据预取到内存提前形成多级缓存</strong>；另一方面也是<strong>对运行环境的预热</strong>，载入CPU高速缓存、热点函数JIT编译成机器码等等；</li>
<li><strong>热点资源提前预分配</strong>到各个实例，比如：秒杀、售票的<strong>库存性质的数据</strong>；分布式<strong>唯一ID</strong>等等。</li>
</ul>
<p>天上不会掉馅饼，<strong>预取也是有副作用的</strong>。正如烤箱预热需要消耗时间和额外的电费，在软件代码中做预取/预热的副作用通常是启动慢一些、占用一些闲时的计算资源、可能取到的<strong>不一定是后面需要的</strong>。</p>
<h2 id="削峰填谷术" tabindex="-1">削峰填谷术 <a class="header-anchor" href="#削峰填谷术" aria-label="Permalink to &quot;削峰填谷术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/peakload.png" alt=""></p>
<p><strong>削峰填谷</strong>的原理也是“<strong>时间换时间</strong>”，<strong>谷时换峰时</strong>。削峰填谷与<strong>预取</strong>是反过来的：预取是事先花时间做，削峰填谷是事后花时间做。就像三峡大坝可以抗住短期巨量洪水，事后雨停再慢慢开闸防水。软件世界的“削峰填谷”是类似的，只是不是用三峡大坝实现，而是用消息队列、异步化等方式。</p>
<p>常见的有这几类问题，我们分别来看每种对应的解决方案：</p>
<ul>
<li>针对前端、客户端的<strong>启动优化或首屏优化</strong>：代码和数据等资源的<strong>延时加载、分批加载、后台异步加载、或按需懒加载</strong>等等。</li>
<li><strong>背压控制</strong> - <strong>限流、节流、去抖</strong>等等。一夫当关，万夫莫开，从<strong>入口处削峰</strong>，防止一些恶意重复请求以及请求过于频繁的爬虫，甚至是一些DDoS攻击。简单做法有网关层根据单个IP或用户用漏桶控制请求速率和上限；前端做按钮的节流去抖防止重复点击；网络层开启TCP SYN Cookie防止恶意的SYN洪水攻击等等。彻底杜绝爬虫、黑客手段的恶意洪水攻击是很难的，DDoS这类属于网络安全范畴了。</li>
<li>针对正常的业务请求洪峰，<strong>用消息队列暂存再异步化处理</strong>：常见的后端消息队列<strong>Kafka、RocketMQ</strong>甚至Redis等等都可以做缓冲层，第一层业务处理直接校验后丢到消息队列中，在洪峰过去后慢慢消费消息队列中的消息，执行具体的业务。另外执行过程中的耗时和耗计算资源的操作，也可以丢到消息队列或数据库中，等到谷时处理。</li>
<li><strong>捋平毛刺</strong>：有时候洪峰不一定来自外界，如果系统内部大量<strong>定时任务</strong>在同一时间执行，或与业务高峰期重合，很容易在监控中看到“毛刺”——短时间负载极高。一般解决方案就是错峰执行定时任务，或者分配到其他非核心业务系统中，把“毛刺”摊平。比如很多数据分析型任务都放在业务低谷期去执行，大量定时任务在创建时尽量加一些随机性来分散执行时间。</li>
<li><strong>避免错误风暴带来的次生洪峰</strong>：有时候网络抖动或短暂宕机，业务会出现各种异常或错误。这时处理不好很容易带来<strong>次生灾害</strong>，比如：很多代码都会做错误重试，不加控制的大量重试甚至会导致网络抖动恢复后的瞬间，积压的大量请求再次冲垮整个系统；还有一些代码没有做超时、降级等处理，可能导致大量的等待耗尽TCP连接，进而导致整个系统被冲垮。解决之道就是做限定次数、间隔指数级增长的Back-Off重试，设定超时、降级策略。</li>
</ul>
<h2 id="批量处理术" tabindex="-1">批量处理术 <a class="header-anchor" href="#批量处理术" aria-label="Permalink to &quot;批量处理术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/batch-op.png" alt=""></p>
<p><strong>批量处理</strong>同样可以看成“<strong>时间换时间</strong>”，其原理是<strong>减少了重复的事情，是一种对执行流程的压缩</strong>。以<strong>个别批量操作更长的耗时为代价，在整体上换取了更多的时间</strong>。</p>
<p>批量处理的应用也非常广泛，我们还是从前端开始讲：</p>
<ul>
<li>打包合并的JS文件、雪碧图等等，将<strong>一批资源</strong>集中到一起，<strong>一次性传输</strong>；</li>
<li>前端动画使用requestAnimationFrame在UI渲染时<strong>批量处理积压的变化</strong>，而不是有变化立刻更新，在游戏开发中也有类似的应用；</li>
<li>前后端中使用<strong>队列暂存临时产生的数据</strong>，积压到一定数量再批量处理；</li>
<li>在不影响可扩展性情况下，<strong>一个接口传输多种需要的数据</strong>，减少大量ajax调用（<strong>GraphQL</strong>在这一点就做到了极致）；</li>
<li><strong>系统间通信尽量发送整批数据</strong>，比如<strong>消息队列的发布订阅、存取缓存服务的数据、RPC调用、插入或更新数据库</strong>等等，能批量做尽可能批量做，因为这些系统间通信的I/O时间开销已经很昂贵了；</li>
<li><strong>数据积压到一定程度再落盘</strong>，操作系统本身的写文件就是这么做的，Linux的fwrite只是写入缓冲区暂存，积压到一定程度再fsync刷盘。在应用层，很多高性能的数据库和K-V存储的实现都体现了这一点：一些NoSQL的LSM Tree的第一层就是在内存中先积压到一定大小再往下层合并；Redis的RDB结合AOF的落盘机制；Linux系统调用也提供了批量读写多个缓冲区文件的系统调用：readv/writev；</li>
<li><strong>延迟地批量回收资源</strong>，比如JVM的Survivor Space的S0和S1区互换、Redis的Key过期的清除策略。</li>
</ul>
<p>批量处理如此好用，那么问题来了，<strong>每一批放多大最合适呢</strong>？</p>
<p>这个问题其实没有定论，有一些个人经验可以分享。</p>
<ul>
<li>前端把所有文件打包成单个JS，大部分时候并不是最优解。Webpack提供了很多分块的机制，CSS和JS分开、JS按业务分更小的Chunk结合懒加载、一些体积大又不用在首屏用的第三方库设置external或单独分块，可能整体性能更高。不一定要一批搞定所有事情，分几个小批次反而用户体验的性能更好。</li>
<li>Redis的<strong>MGET、MSET</strong>来批量存取数据时，每批大小<strong>不宜过大</strong>，因为Redis主线程只有一个，如果一批太大执行期间会让其他命令无法响应。经验上一批50-100个Key性能是不错的，但最好在真实环境下用真实大小的数据量化度量一下，做Benchmark测试才能确定一批大小的最优值。</li>
<li>MySQL、Oracle这类RDBMS，最优的批量Insert的大小也视数据行的特性而定。我之前在2U8G的Oracle上用一些普遍的业务数据做过测试，批量插入时每批5000-10000条数据性能是最高的，每批过大会导致DML的解析耗时过长，甚至单个SQL语句体积超限，单批太多反而得不偿失。</li>
<li>消息队列的发布订阅，每批的消息长度尽量控制在1MB以内，有些云服务商提供的消息队列限制了最大长度，那这个长度可能就是<strong>性能拐点</strong>，比如AWS的SQS服务对单条消息的限制是256KB。</li>
</ul>
<p>总之，多大一批可以确保单批响应时间不太长的同时让整体性能最高，是需要在实际情况下做基准测试的，不能一概而论。而批量处理的<strong>副作用</strong>在于：处理逻辑会更加复杂，尤其是一些涉及事务、并发的问题；需要用数组或队列用来存放缓冲一批数据，消耗了额外的存储空间。</p>
<h2 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h2>
<p>上半部分先聊到这里，大都是“时间”与“空间”的取舍之术，这些思路在很多地方甚至是非软件领域都是<strong>普适</strong>的。下半部分我们再聊一些不完全普适、稍微进阶一点的性能优化的技术路线。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——性能优化的十种手段（中篇）]]></title>
            <link>https://code2life.top/blog/0056-performance2</link>
            <guid>https://code2life.top/blog/0056-performance2</guid>
            <pubDate>Fri, 14 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——性能优化的十种手段-中篇" tabindex="-1">软件设计杂谈——性能优化的十种手段（中篇） <a class="header-anchor" href="#软件设计杂谈——性能优化的十种手段-中篇" aria-label="Permalink to &quot;软件设计杂谈——性能优化的十种手段（中篇）&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="索引" tabindex="-1">索引 <a class="header-anchor" href="#索引" aria-label="Permalink to &quot;索引&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="/blog/0055-performance.html">软件设计杂谈——性能优化的十种手段（上篇）</a></li>
<li><a href="/blog/0056-performance2.html">软件设计杂谈——性能优化的十种手段（中篇）</a></li>
<li><a href="/blog/0056-performance3.html">软件设计杂谈——性能优化的十种手段（下篇）</a></li>
</ul>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p><a href="/blog/0055-performance.html">上一篇</a>，我们总结了六种<strong>普适</strong>的性能优化方法，包括 <strong>索引、压缩、缓存、预取、削峰填谷、批量处理</strong>，简单讲解了每种技术手段的原理和实际应用。在开启最后一篇前，我们先需要搞清楚：在程序运行期间，<strong>时间和空间都耗在哪里了</strong>？</p>
<h3 id="时间都去哪儿了" tabindex="-1">时间都去哪儿了？ <a class="header-anchor" href="#时间都去哪儿了" aria-label="Permalink to &quot;时间都去哪儿了？&quot;">&ZeroWidthSpace;</a></h3>
<p>人眨一次眼大约100毫秒，而现代1核CPU在一眨眼的功夫就可以执行<strong>数亿条</strong>指令。</p>
<p>现代的CPU已经非常厉害了，频率已经达到了GHz级别，也就是每秒数十亿个指令周期。</p>
<p>即使一些CPU指令需要多个时钟周期，但由于有<strong>流水线机制</strong>的存在，平均下来<strong>大约每个时钟周期能执行1条指令</strong>，比如一个3GHz频率的CPU核心，每秒大概可以执行<strong>20亿到40亿</strong>左右的指令数量。</p>
<p>程序运行还需要RAM，也可能用到持久化存储，网络等等。随着新的技术和工艺的出现，这些硬件也越来越厉害，比如CPU高速缓存的提升、NVMe固态硬盘相对SATA盘读写速率和延迟的飞跃等等。这些硬件具体有多强呢？</p>
<p>有一个非常棒的网站“Latency Numbers Every Programmer Should Know”，可以直观地查看从1990年到现在，<strong>高速缓存、内存、硬盘、网络时间开销的具体数值</strong>。</p>
<p><a href="https://colin-scott.github.io/personal_website/research/interactive_latency.html" target="_blank" rel="noreferrer">https://colin-scott.github.io/personal_website/research/interactive_latency.html</a></p>
<p>下图是2020年的截图，的确是“<strong>每个开发者应该知道的数字</strong>”。</p>
<p><img src="//filecdn.code2life.top/perf-num1.png" alt="">
<img src="//filecdn.code2life.top/perf-num2.png" alt=""></p>
<p>这里有几个非常关键的数据：</p>
<ul>
<li>存取一次CPU多级高速缓存的时间大约<strong>1-10纳秒</strong>级别；</li>
<li>存取一次主存(RAM)的时间大概在<strong>100纳秒</strong>级别；</li>
<li>固态硬盘的一次随机读写大约在<strong>10微秒到1毫秒</strong>这个数量级；</li>
<li>网络包在局域网传输一个来回大约是<strong>0.5毫秒</strong>。</li>
</ul>
<p>看到不同硬件之间<strong>数量级的差距</strong>，就很容易理解性能优化的一些技术手段了。</p>
<p>比如一次网络传输的时间，是主存访问的<strong>5000倍</strong>，明白这点就不难理解写for循环发HTTP请求，为什么会被扣工资了。</p>
<p>放大到我们容易感知的时间范围，来理解5000倍的差距：<strong>如果一次主存访问是1天的话，一趟局域网数据传输就要13.7年</strong>。</p>
<p>如果要传输更多网络数据，每两个网络帧之间还有固定的<strong>间隔</strong>（<a href="https://en.wikipedia.org/wiki/Interpacket_gap" target="_blank" rel="noreferrer">Interpacket Gap</a>），在间隔期间传输Idle信号，数据链路层以此来区分两个数据包，具体数值在链接Wiki中有，这里截取几个我们熟悉的网络来感受一下：</p>
<ul>
<li>百兆以太网: 0.96 µs</li>
<li>千兆以太网：96 ns</li>
<li>万兆以太网：9.6 ns</li>
</ul>
<p>不过，单纯看硬件的上限意义不大，从代码到机器指令中间有许多层抽象，仅仅是在TCP连接上发一个字节的数据包，从操作系统内核到网线，涉及到的基础设施级别的软硬件不计其数。到了应用层，单次操作耗时虽然没有非常精确的数字，但<strong>经验上的范围</strong>也值得参考：</p>
<ul>
<li>用Memcached/Redis存取缓存数据：<strong>1-5 ms</strong></li>
<li>执行一条简单的数据库查询或更新操作：<strong>5-50ms</strong></li>
<li>在局域网中的TCP连接上收发一趟数据包：<strong>1-10ms</strong>；广域网中大约<strong>10-200ms</strong>，视传输距离和网络节点的设备而定</li>
<li>从用户态切换到内核态，完成一次系统调用：<strong>100ns - 1 μs</strong>，视不同的系统调用函数和硬件水平而定，少数系统调用可能远超此范围。</li>
</ul>
<h3 id="空间都去哪儿了" tabindex="-1">空间都去哪儿了？ <a class="header-anchor" href="#空间都去哪儿了" aria-label="Permalink to &quot;空间都去哪儿了？&quot;">&ZeroWidthSpace;</a></h3>
<p>在计算机历史上，非易失存储技术的发展速度超过了摩尔定律。除了嵌入式设备、数据库系统等等，现在大部分场景已经不太需要优化持久化存储的空间占用了，这里主要讲的是另一个<strong>相对稀缺</strong>的存储形式 —— RAM，或者说主存/内存。</p>
<p>以JVM为例，在<strong>堆里面</strong>有很多我们创建的对象（Object）。</p>
<ul>
<li>每个Object都有一个包含Mark和类型指针的Header，占<strong>12个字节</strong></li>
<li>每个成员变量，根据数据类型的不同占不同的字节数，如果是另一个对象，其对象指针占<strong>4个字节</strong></li>
<li>数组会根据声明的大小，占用<strong>N倍于其类型Size</strong>的字节数</li>
<li>成员变量之间需要对齐到<strong>4字节</strong>，每个对象之间需要对齐到<strong>8字节</strong></li>
</ul>
<p>如果在32G以上内存的机器上，禁用了<strong>对象指针压缩</strong>，对象指针会变成<strong>8字节</strong>，包括Header中的Klass指针，这也就不难理解为什么堆内存超过<strong>32G</strong>，JVM的性能直线下降了。</p>
<p>举个例子，一个有8个int类型成员的对象，需要占用48个字节（12+32+4），如果有<strong>十万</strong>个这样的Object，就需要占用<strong>4.58MB的内存</strong>了。这个数字似乎看起来不大，而实际上一个Java服务的堆内存里面，各种各样的对象占用的内存通常比这个数字多得多，大部分内存耗在char[]这类数组或集合型数据类型上。</p>
<p><strong>堆内存之外，又是另一个世界了</strong>。</p>
<p>从操作系统进程的角度去看，也有不少耗内存的<strong>大户</strong>，不管什么Runtime都逃不开这些空间开销：每个线程需要分配MB级别的<strong>线程栈</strong>，运行的程序和数据会<strong>缓存</strong>下来，用到的输入输出设备需要<strong>缓冲区</strong>……</p>
<p><strong>代码“写出来”的内存占用，仅仅是冰山之上的部分</strong>，真正的内存占用比“写出来”的要更多，到处都存在<strong>空间利用率</strong>的问题。</p>
<p>比如，即使我们在Java代码中只是写了 response.getWriter().print(&quot;OK&quot;)，给浏览器返回2字节，网络协议栈的层层封装，<strong>协议头部不断增加的额外数据</strong>，让最终返回给浏览器的字节数远超原始的2字节，像IP协议的报头部就<strong>至少有20个字节</strong>，而数据链路层的一个<strong>以太网帧头部至少有18字节</strong>。</p>
<p>如果传输的数据过大，各层协议还有<strong>最大传输单元MTU</strong>的限制，IPv4一个报文最大只能有<strong>64K比特</strong>，超过此值需要分拆发送并在接收端组合，更多额外的报头导致空间利用率降低（IPv6则提供了Jumbogram机制，最大单包4G比特，“浪费”就减少了）。</p>
<p><strong>这部分的“浪费”有多大呢</strong>？下面的链接有个表格，传输<strong>1460个字节</strong>的载荷，经过有线到无线网络的转换，<strong>至少再添120个字节</strong>，<strong>空间利用率&lt;92.4%</strong>。</p>
<p><a href="https://en.wikipedia.org/wiki/Jumbo_frame" target="_blank" rel="noreferrer">https://en.wikipedia.org/wiki/Jumbo_frame</a></p>
<p>这种现象非常普遍，使用抽象层级越高的技术平台，平台提供高级能力的同时，其底层实现的“信息密度”通常越低。像Java的Object Header就是使用JVM的代价，而更进一步使用动态类型语言，要为<strong>灵活性付出空间的代价</strong>则更大。哈希表的自动扩容，强大的反射能力等等，背后也付出了空间的代价。</p>
<p>再比如，二进制数据交换协议通常比纯文本协议更加节约空间。但多数厂家我们仍然用JSON、XML等纯文本协议，用<strong>信息的冗余</strong>来换取<strong>可读性</strong>。即便是二进制的数据交互格式，也会存在信息冗余，只能通过更好的协议和压缩算法，尽量去逼近压缩的极限 —— 信息熵。</p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>理解了时间和空间的消耗在哪后，还不能完全解释软件为何倾向于<strong>耗尽</strong>硬件资源。有一条定律可以解释，正是它<strong>锤爆</strong>了摩尔定律。</p>
<p>它就是<strong>安迪-比尔定律</strong>。</p>
<blockquote>
<p>“安迪给什么，比尔拿走什么”。</p>
</blockquote>
<p>安迪指的是Intel前CEO安迪·葛洛夫，比尔指的是比尔·盖茨。这句话的意思就是：<strong>软件发展比硬件还快，总能吃得下硬件</strong>。20年前，在最强的计算机也不见得可以玩赛车游戏；10年前，个人电脑已经可以玩画质还可以的3D赛车游戏了；现在，自动驾驶+5G云驾驶已经快成为现实。在这背后，是无数的硬件技术飞跃，以及吃掉了这些硬件的各类软件。这也是我们每隔两三年都要换手机的原因：<strong>不是机器老化变卡了，是嗜血的软件在作怪</strong>。</p>
<p><img src="//filecdn.code2life.top/eat-mem.png" alt=""></p>
<p>因此，即使现代的硬件水平已经强悍到如此境地，性能优化仍然是<strong>有必要的</strong>。软件日益复杂，<strong>抽象层级越来越高，就越需要底层基础设施被充分优化</strong>。对于大部分开发者而言，高层代码逐步走向低代码化、可视化，<strong>“一行代码”能产生的影响也越来越大，写出低效代码则会吃掉更多的硬件资源</strong>。</p>
<h2 id="参考" tabindex="-1">参考 <a class="header-anchor" href="#参考" aria-label="Permalink to &quot;参考&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="https://colin-scott.github.io/personal_website/research/interactive_latency.html" target="_blank" rel="noreferrer">https://colin-scott.github.io/personal_website/research/interactive_latency.html</a></li>
<li><a href="https://en.wikipedia.org/wiki/Interpacket_gap" target="_blank" rel="noreferrer">https://en.wikipedia.org/wiki/Interpacket_gap</a></li>
<li><a href="https://www.baeldung.com/java-memory-layout" target="_blank" rel="noreferrer">https://www.baeldung.com/java-memory-layout</a></li>
<li><a href="https://tools.ietf.org/html/rfc791" target="_blank" rel="noreferrer">https://tools.ietf.org/html/rfc791</a></li>
<li><a href="https://en.wikipedia.org/wiki/Andy_and_Bill%27s_law" target="_blank" rel="noreferrer">https://en.wikipedia.org/wiki/Andy_and_Bill's_law</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——性能优化的十种手段（下篇）]]></title>
            <link>https://code2life.top/blog/0056-performance3</link>
            <guid>https://code2life.top/blog/0056-performance3</guid>
            <pubDate>Thu, 13 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——性能优化的十种手段-下篇" tabindex="-1">软件设计杂谈——性能优化的十种手段（下篇） <a class="header-anchor" href="#软件设计杂谈——性能优化的十种手段-下篇" aria-label="Permalink to &quot;软件设计杂谈——性能优化的十种手段（下篇）&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="索引" tabindex="-1">索引 <a class="header-anchor" href="#索引" aria-label="Permalink to &quot;索引&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="/blog/0055-performance.html">软件设计杂谈——性能优化的十种手段（上篇）</a>，我们总结了六种<strong>普适</strong>的性能优化方法，包括 <strong>索引、压缩、缓存、预取、削峰填谷、批量处理</strong>，简单讲解了每种技术手段的原理和实际应用；</li>
<li><a href="/blog/0056-performance2.html">软件设计杂谈——性能优化的十种手段（中篇）</a>，我们简单了解了程序是如何消耗执行时间和内存空间的；</li>
<li><a href="/blog/0056-performance3.html">软件设计杂谈——性能优化的十种手段（下篇）</a>，再讲另外几类<strong>涉及更多技术细节</strong>的性能优化方向。</li>
</ul>
<p>本篇也是本系列最硬核的一篇，本人技术水平有限，可能存在疏漏或错误之处，望斧正。仍然选取了《火影忍者》的配图和命名方式帮助理解：</p>
<ul>
<li>八门遁甲 —— 榨干计算资源</li>
<li>影分身术 —— 水平扩容</li>
<li>奥义 —— 分片术</li>
<li>秘术 —— 无锁术</li>
</ul>
<p>（注：这些“中二”的前缀仅是用《火影》中的一些术语，形象地描述技术方案）</p>
<h2 id="八门遁甲-——-榨干计算资源" tabindex="-1">八门遁甲 —— 榨干计算资源 <a class="header-anchor" href="#八门遁甲-——-榨干计算资源" aria-label="Permalink to &quot;八门遁甲 —— 榨干计算资源&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/eight-gate.jpg" alt=""></p>
<p><strong>让硬件资源都在处理真正有用的逻辑计算，而不是做无关的事情或空转</strong>。</p>
<p>从晶体管到集成电路、驱动程序、操作系统、直到高级编程语言的层层抽象，每一层抽象带来的<strong>更强的通用性</strong>、<strong>更高的开发效率</strong>，多是以<strong>损失运行效率</strong>为代价的。但我们可以在用高级编程语言写代码的时候，在<strong>保障可读性、可维护性基础上</strong>用<strong>运行效率更高、更适合运行时环境</strong>的方式去写，<strong>减少额外的性能损耗</strong>《Effective XXX》、《More Effective XXX》、《高性能XXX》这类书籍所传递的知识和思想。</p>
<p>落到技术细节，下面用四个小节来说明如何减少“无用功”、避免空转、榨干硬件。</p>
<h2 id="聚焦" tabindex="-1">聚焦 <a class="header-anchor" href="#聚焦" aria-label="Permalink to &quot;聚焦&quot;">&ZeroWidthSpace;</a></h2>
<p>减少系统调用与上下文切换，让CPU聚焦。</p>
<p><a href="https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch" target="_blank" rel="noreferrer">https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch</a>
<a href="https://stackoverflow.com/questions/23599074/system-calls-overhead" target="_blank" rel="noreferrer">https://stackoverflow.com/questions/23599074/system-calls-overhead</a></p>
<p>less copy, less context switch, less system call
fsync 10-50ms, ssd 100-10000μs (SATA NVME)<br>
ctx switch : system call -&gt; mode switch,  thread switch: cache change, work set change, full ctx switch (1-30 μs)</p>
<p>大部分互联网应用服务，耗时的部分不是计算，而是I/O。</p>
<p>减少I/O wait， 各司其职，专心干I/O，专心干计算，epoll批量捞任务，（refer: event driven）</p>
<p><a href="//jolestar.com/parallel-programming-model-thread-goroutine-actor/" target="_blank" rel="noreferrer">//jolestar.com/parallel-programming-model-thread-goroutine-actor/</a></p>
<ul>
<li>利用DMA减少CPU负担 - 零拷贝 NewI/O Redis SingleThread (even 6.0), Node.js</li>
</ul>
<p>避免不必要的调度 - Context Switch</p>
<p>CPU亲和性，让CPU更加聚焦</p>
<h4 id="蜕变" tabindex="-1">蜕变 <a class="header-anchor" href="#蜕变" aria-label="Permalink to &quot;蜕变&quot;">&ZeroWidthSpace;</a></h4>
<p>用更高效的数据结构、算法、第三方组件，让程序本身蜕变。</p>
<p>从逻辑短路、Map代替List遍历、减少锁范围、这样的编码技巧，到应用FisherYates、Dijkstra这些经典算法，注意每一行代码细节，量变会发生质变。更何况某个算法就足以让系统性能产生一两个数量级的提升。</p>
<h4 id="适应" tabindex="-1">适应 <a class="header-anchor" href="#适应" aria-label="Permalink to &quot;适应&quot;">&ZeroWidthSpace;</a></h4>
<p>因地制宜，适应特定的运行环境</p>
<p>在浏览器中主要是优化方向是I/O、UI渲染引擎、JS执行引擎三个方面。I/O越少越好，能用WebSocket的地方就不用Ajax，能用Ajax的地方就不要刷整个页面；UI渲染方面，减少重排和重绘，比如Vue、React等MVVM框架的虚拟DOM用额外的计算换取最精简的DOM操作；JS执行引擎方面，少用动态性极高的写法，比如eval、随意修改对象或对象原型的属性。前端的优化有个神器：<a href="https://github.com/GoogleChrome/lighthouse" target="_blank" rel="noreferrer">Light House</a>，在新版本Chrome已经嵌到开发者工具中了，可以一键生成性能优化报告，按照优化建议改就完了。</p>
<p>与浏览器环境颇为相似的Node.js环境，
<a href="https://segmentfault.com/a/1190000007621011#articleHeader11" target="_blank" rel="noreferrer">https://segmentfault.com/a/1190000007621011#articleHeader11</a></p>
<p>Java</p>
<p>C1 C2 JIT编译器
栈上分配</p>
<p>Linux</p>
<ul>
<li>各种参数优化</li>
<li>内存分配和GC策略</li>
<li>Linux内核参数  Brendan Gregg
内存区块配置（DB，JVM，V8，etc.）</li>
</ul>
<p>利用语言特性和运行时环境 - 比如写出利于JIT的代码</p>
<ul>
<li>多静态少动态 - 舍弃动态特性的灵活性 - hardcode/if-else，强类型，弱类型语言避免类型转换  AOT/JIT vs 解释器， 汇编，机器码 GraalVM</li>
</ul>
<p>减少内存的分配和回收，少对列表做增加或删除</p>
<p>对于RAM有限的嵌入式环境，有时候时间不是问题，反而要拿时间换空间，以节约RAM的使用。</p>
<h4 id="运筹" tabindex="-1">运筹 <a class="header-anchor" href="#运筹" aria-label="Permalink to &quot;运筹&quot;">&ZeroWidthSpace;</a></h4>
<p>把眼界放宽，跳出程序和运行环境本身，从整体上进行系统性分析最高性价比的优化方案，分析潜在的优化切入点，以及能够调配的资源和技术，运筹帷幄。</p>
<p>其中最简单易行的几个办法，就是花钱，买更好或更多的硬件基础设施，这往往是开发人员容易忽视的，这里提供一些妙招：</p>
<ul>
<li>服务器方面，云服务厂商提供各种类型的实例，每种类型有不同的属性侧重，带宽、CP、磁盘的I/O能力，选适合的而不是更贵的</li>
<li>舍弃虚拟机 - Bare Mental，比如神龙服务器</li>
<li>用ARM架构CPU的服务器，同等价格可以买到更多的服务器，对于多数可以跨平台运行的服务端系统来说与x86区别并不大，ARM服务器的数据中心也是技术发展趋势使然</li>
<li>如果必须用x86系列的服务器，AMD也Intel的性价比更高。</li>
</ul>
<p>第一点非常重要，软件性能遵循木桶原理，一定要找到瓶颈在哪个硬件资源，把钱花在刀刃上。如果是服务端带宽瓶颈导致的性能问题，升级再多核CPU也是没有用的。我有一次性能优化案例：把一个跑复杂业务的Node.js服务器从AWS的m4类型换成c4类型，内存只有原来的一半，但CPU使用率反而下降了20%，同时价格还比之前更便宜，一石二鸟。</p>
<p>这是因为Node.js主线程的计算任务只有一个CPU核心在干，通过CPU Profile的火焰图，可以定位到该业务的瓶颈在主线程的计算任务上，因此提高单核频率的作用是立竿见影的。而该业务对内存的消耗并不多，套用一些定制v8引擎内存参数的方案，起不了任何作用。</p>
<p>毕竟这样的例子不多，大部分时候还是要多花钱买更高配的服务器的，除了这条花钱能直接解决问题的办法，剩下的办法难度就大了：</p>
<ul>
<li>利用更底层的特性实现功能，比如FFI WebAssembly调用其他语言，Java Agent Instrument，字节码生成（BeanCopier, Json Lib），甚至汇编等等</li>
<li>使用硬件提供的更高效的指令</li>
<li>各种提升TLB命中率的机制，减少内存的大页表</li>
<li>魔改Runtime，Facebook的PHP，阿里腾讯定制的JDK</li>
<li>网络设备参数，MTU</li>
<li>专用硬件：GPU加速（cuda）、AES硬件卡和高级指令加速加解密过程，比如TLS</li>
<li>可编程硬件：地狱级难度，FPGA硬件设备加速特定业务</li>
<li>NUMA</li>
<li>更宏观的调度，VM层面的共享vCPU，K8S集群调度，总体上的优化</li>
</ul>
<h4 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h4>
<p>有些手段，是凭空换出来更多的空间和时间了吗？天下没有免费的午餐，即使那些看起来空手套白狼的优化技术，也需要额外的人力成本来做，副作用可能就是专家级的发际线吧。还好很多复杂的性能优化技术我也不会，所以我本人发际线还可以。</p>
<p>这一小节总结了一些方向，有些技术细节非常深，这里也无力展开。不过，即使榨干了单机性能，也可能不足以支撑业务，这时候就需要分布式集群出场了，因此后面介绍的3个技术方向，都与<strong>并行化</strong>有关。</p>
<h2 id="影分身术-——-水平扩容" tabindex="-1">影分身术 —— 水平扩容 <a class="header-anchor" href="#影分身术-——-水平扩容" aria-label="Permalink to &quot;影分身术 —— 水平扩容&quot;">&ZeroWidthSpace;</a></h2>
<p>本节的<strong>水平扩容</strong>以及下面一节的<strong>分片</strong>，可以算整体的性能提升而不是单点的性能优化，会因为引入额外组件<strong>反而降低了处理单个请求的性能</strong>。但当业务规模大到一定程度时，再好的单机硬件也无法承受流量的洪峰，就得水平扩容了，毕竟&quot;众人拾柴火焰高&quot;。</p>
<p>在这背后的理论基础是，硅基半导体已经接近物理极限，随着摩尔定律的减弱，阿姆达尔定律的作用显现出来，<a href="https://en.wikipedia.org/wiki/Amdahl%27s_law" target="_blank" rel="noreferrer">https://en.wikipedia.org/wiki/Amdahl's_law</a></p>
<p>水平扩容必然引入负载均衡</p>
<p><img src="//filecdn.code2life.top/perf/hpa-yml.png" alt=""></p>
<p>多副本
水平扩容的前提是无状态
读&gt;&gt;写， 多个读实例副本 （CDN）
自动扩缩容，根据常用的或自定义的metrics，判定扩缩容的条件，或根据CRON
负载均衡策略的选择</p>
<p>原理：并行化</p>
<h2 id="奥义-——-分片术" tabindex="-1">奥义 —— 分片术 <a class="header-anchor" href="#奥义-——-分片术" aria-label="Permalink to &quot;奥义 —— 分片术&quot;">&ZeroWidthSpace;</a></h2>
<p>水平扩容针对无状态组件，分片针对有状态组件。二者原理都是提升并行度，但分片的难度更大。负载均衡也不再是简单的加权轮询了，而是进化成了各个分片的<strong>协调器</strong></p>
<p><img src="//filecdn.code2life.top/perf/sharding.png" alt=""></p>
<p>分片 - 百科全书分册
Java1.7的及之前的 ConcurrentHashMap分段锁 <a href="https://www.codercto.com/a/57430.html" target="_blank" rel="noreferrer">https://www.codercto.com/a/57430.html</a>
有状态数据的分片
如何选择Partition/Sharding Key
负载均衡难题
热点数据，增强缓存等级，解决分散的缓存带来的一致性难题
数据冷热分离，SSD - HDD</p>
<p>分开容易合并难</p>
<p>区块链的优化，分区域</p>
<h2 id="秘术-——-无锁术" tabindex="-1">秘术 —— 无锁术 <a class="header-anchor" href="#秘术-——-无锁术" aria-label="Permalink to &quot;秘术 —— 无锁术&quot;">&ZeroWidthSpace;</a></h2>
<p><img src="//filecdn.code2life.top/perf/nolock.png" alt=""></p>
<blockquote>
<p>Don’t communicate by sharing memory, share memory by communicating</p>
</blockquote>
<p>有些业务场景，比如库存业务，按照正常的逻辑去实现，水平扩容带来的提升非常有限，因为需要锁住库存，扣减，再解锁库存。票务系统也类似，为了避免超卖，需要有一把锁禁锢了横向扩展的能力。</p>
<p>不管是单机还是分布式微服务，锁都是制约并行度的一大因素。比如上篇提到的秒杀场景，库存就那么多，系统超卖了可能导致非常大的经济损失，但用分布式锁会导致即使服务扩容了成千上万个实例，最终无数请求仍然阻塞在分布式锁这个串行组件上了，再多水平扩展的实例也无用武之地。</p>
<p>避免竞争Race Condition 是最完美的解决办法。上篇说的应对秒杀场景，预取库存就是减轻竞态条件的例子，虽然取到服务器内存之后仍然有多线程的锁，但锁的粒度更细了，并发度也就提高了。
线程同步锁
分布式锁
数据库锁 update select子句
事务锁
顺序与乱序
乐观锁/无锁 CAS Java 1.8之后的ConcurrentHashMap
pipeline技术 - CPU流水线 Redis Pipeline 大数据分析 并行计算
原理：并行化</p>
<p>TCP的缓冲区排头阻塞 QUIC HTTP3.0</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>以ROI的视角看软件开发，初期人力成本的投入，后期的维护成本，计算资源的费用等等，选一个合适的方案而不是一个性能最高的方案。</p>
<p>本篇结合个人经验总结了常见的性能优化手段，这些手段只是冰山一角。在初期就设计实现出一个完美的高性能系统是不可能的，随着软件的迭代和体量的增大，利用压测，各种工具（profiling，vmstat，iostat，netstat），以及监控手段，逐步找到系统的瓶颈，因地制宜地选择优化手段才是正道。</p>
<p>有利必有弊，得到一些必然会失去一些，有一些手段要慎用。Linux性能优化大师Brendan Gregg一再强调的就是：切忌过早优化、过度优化。</p>
<p>持续观测，做80%高投入产出比的优化。</p>
<p>除了这些设计和实现时可能用到的手段，在技术选型时选择高性能的框架和组件也非常重要。</p>
<p>另外，部署基础设施的硬件性能也同样，合适的服务器和网络等基础设施往往会<strong>事半功倍</strong>，比如云服务厂商提供的各种字母开头的instance，网络设备带宽的速度和稳定性，磁盘的I/O能力等等。</p>
<p>多数时候我们应当使用更高性能的方案，但有时候甚至要故意去违背它们。最后，以《Effective Java》第一章的一句话结束本系列吧。</p>
<blockquote>
<p>首先要学会基本的规则，然后才能知道什么时候可以打破规则。</p>
</blockquote>
<h3 id="参考" tabindex="-1">参考 <a class="header-anchor" href="#参考" aria-label="Permalink to &quot;参考&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li>《高性能JavaScript》 —— Nicholas C. Zakas</li>
<li>《Effective Java》 第三版 —— Joshua Bloch</li>
<li><a href="//www.brendangregg.com/" target="_blank" rel="noreferrer">//www.brendangregg.com/</a> —— Brendan Gregg</li>
<li><a href="https://colin-scott.github.io/personal_website/research/interactive_latency.html" target="_blank" rel="noreferrer">https://colin-scott.github.io/personal_website/research/interactive_latency.html</a></li>
<li><a href="https://stackoverflow.com/questions/23599074/system-calls-overhead" target="_blank" rel="noreferrer">https://stackoverflow.com/questions/23599074/system-calls-overhead</a></li>
<li><a href="https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch" target="_blank" rel="noreferrer">https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch</a></li>
<li><a href="//jolestar.com/parallel-programming-model-thread-goroutine-actor/" target="_blank" rel="noreferrer">//jolestar.com/parallel-programming-model-thread-goroutine-actor/</a></li>
<li><a href="https://www.codercto.com/a/57430.html" target="_blank" rel="noreferrer">https://www.codercto.com/a/57430.html</a></li>
<li><a href="https://segmentfault.com/a/1190000007621011#articleHeader11" target="_blank" rel="noreferrer">https://segmentfault.com/a/1190000007621011#articleHeader11</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——事件驱动]]></title>
            <link>https://code2life.top/blog/0054-polling-to-event-driven</link>
            <guid>https://code2life.top/blog/0054-polling-to-event-driven</guid>
            <pubDate>Wed, 12 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——事件驱动" tabindex="-1">软件设计杂谈——事件驱动 <a class="header-anchor" href="#软件设计杂谈——事件驱动" aria-label="Permalink to &quot;软件设计杂谈——事件驱动&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="总有一个轮询" tabindex="-1">总有一个轮询 <a class="header-anchor" href="#总有一个轮询" aria-label="Permalink to &quot;总有一个轮询&quot;">&ZeroWidthSpace;</a></h2>
<p>如何获取数据的实时变化？这是在软件设计和实现时很常见的问题。一个简单粗暴的思路是：while (true) 一把梭，轮询变化。</p>
<p>有人可能会说，这种方式太低效了不要用，但实际上这可能是异步情况下<strong>唯一</strong>的办法。</p>
<p>先从浏览器的事件机制说起。</p>
<h4 id="前端如何获取数据的变化" tabindex="-1">前端如何获取数据的变化 <a class="header-anchor" href="#前端如何获取数据的变化" aria-label="Permalink to &quot;前端如何获取数据的变化&quot;">&ZeroWidthSpace;</a></h4>
<p>绝大部分JavaScript代码是运行在浏览器的JS主线程中的（除了Web Worker这样的代码），这个主线程的核心就是<strong>事件循环</strong>。Event Loop的实现可以简单地认为也是while true一把梭，不停从<strong>宏任务队列和微任务队列</strong>取出要执行的函数，取到了就压入栈中执行。宏任务包括DOM事件、网络IO、定时器等；微任务包括Promise、MutationObserver等。</p>
<p>前端代码要想知道<strong>用户输入数据或DOM的实时变化</strong>，通过浏览器提供的API注册EventListener即可，Vue、React这类MVVM框架提供了更完善的数据驱动机制，但框架实现也需要注册EventListener。事件回调机制看似不是在轮询，变化直接调用回调函数了，但底层仍是依托于浏览器中的Event Loop轮询任务队列实现的。</p>
<p>前端要知道<strong>后端数据的实时变化</strong>，在没有WebSocket的年代会用长轮询（Long-Polling）这种机制，服务端在变化发生时，用当前保持的HTTP连接来返回变化的数据，浏览器端收到网络I/O事件放入宏任务队列，下次轮询触发回调；在有WebSocket之后，长轮询用于一直保持HTTP连接的无限循环被干掉了，但WebSocket的I/O事件仍然是由Event Loop拿过去的，区别只是少了一个循环，节约了一些无谓的HTTP请求响应的开销。</p>
<p>前端简单聊了，可以得出：要知道实时变化，总有一个while true是跑不掉的。那么后端服务之间是如何知道数据的实时变化呢？</p>
<h4 id="后端如何获取数据的变化" tabindex="-1">后端如何获取数据的变化 <a class="header-anchor" href="#后端如何获取数据的变化" aria-label="Permalink to &quot;后端如何获取数据的变化&quot;">&ZeroWidthSpace;</a></h4>
<p>后端服务经常面临一个问题，在分布式系统中尤其明显：<strong>A服务改了数据X，如何通知到依赖数据X的B服务</strong>？</p>
<p>不太好的办法是B服务不停地轮询A服务：数据X变了没有，变了没有，变了没有？这种轮询是低效的，因为有大量请求是无用的，并且A强依赖B了。好一点的一般有两个办法：</p>
<ul>
<li>A直接告诉B，A通过同步的RPC或HTTP调用B；</li>
<li>A把消息放出去，B通过同步或异步的消息队列去订阅。</li>
</ul>
<p>当该业务实现是A服务强依赖B服务并且一定要同步调用时，用前者是合适的；当A服务不需要强依赖B时，引入消息队列干掉A、B之间的耦合，用后者是比较好的。</p>
<p>前者可以视作Subject通知Observer的<strong>观察者模式</strong>，后者引入了中介的Broker可以视作<strong>发布订阅模式</strong>，都不用在业务上轮询了。然而这两种方式中，真的没有&quot;while true&quot;的存在了吗？</p>
<h4 id="藏在bio、nio、i-o多路复用中的轮询" tabindex="-1">藏在BIO、NIO、I/O多路复用中的轮询 <a class="header-anchor" href="#藏在bio、nio、i-o多路复用中的轮询" aria-label="Permalink to &quot;藏在BIO、NIO、I/O多路复用中的轮询&quot;">&ZeroWidthSpace;</a></h4>
<p>其实，这一顿操作，是把<strong>上层数据的变化</strong>的事件转换成了<strong>底层网络数据包读写</strong>的事件，复用了底层的“I/O事件循环”，进而避免业务层用循环+Sleep的轮询带来的开销，并具有更高的实时性。那么，底层网络数据包变化的&quot;while true&quot;轮询到底在哪里呢？</p>
<p>刚学编程时我们会学怎么写一个TCP Server，其中最核心的逻辑就是接收Socket连接，不停调用read轮询Socket的缓冲区数据，大概就像下面这样（只是示意，实际上这样是跑不通的）。</p>
<div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// declare vars, create socket, bind, listen, etc.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  childfd </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> accept</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(parentfd, (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sockaddr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">clientaddr, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">clientlen);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    n </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> read</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(childfd, buf, BUFSIZE);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>这样最简单的<strong>阻塞式I/O</strong>（BIO）中的轮询我们找到了，但轮询占用了当前线程，而且每个Socket都要一个轮询来读取缓冲区，效率是很低下的。</p>
<p>我们再看看异步非阻塞的NodeJS，其实NodeJS的执行机制与浏览器非常相似，也是以Event Loop为核心的，只是经常作为服务端或者命令行程序的NodeJS程序，要处理的事件大部分和UI无关，更多地与文件和网络打交道。在代码里只要写一个注册事件和callback函数即可处理网络数据的变化，比如下面是一个最简化的NodeJS TCP Server的例子（这个是可以跑的）。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'net'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">createServer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">socket</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  socket.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">buffer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // got data in callback function !</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">listen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'127.0.0.1'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>代码里没有看到&quot;while true&quot;循环，并不代表异步I/O就不用轮询了，只是<strong>藏得更深</strong>。NodeJS的libuv实现了一个跨平台的异步I/O库，封装了底层<strong>非阻塞式I/O</strong>（NIO）和<strong>I/O多路复用</strong>（Multiplexing）的细节。</p>
<p>不只是NodeJS依赖的libuv，其他语言和框架提供基于Event Loop模型的I/O实现都是类似的：<strong>创建非阻塞式的Socket连接避免主线程Block，再加上I/O多路复用技术轮询一组Socket的事件</strong>（在Windows下是IOCP，在Linux下一般用epoll，在Free BSD/MacOS下是kqueue），下面是Windows和Linux下的不严谨的简单示意代码，还是可以找到&quot;while true&quot;轮询嘛。</p>
<div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ===== Windows IOCP ====== //</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 使用Non-Blocking的Socket</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WSASocket</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..) </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // 创建事件消息队列</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CreateIoCompletionPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 轮询设备句柄完成端口的状态（Windows风格翻译）</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  GetQueuedCompletionStatus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..); </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // handle read/write</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ===== Linux epoll ======= //</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 使用Non-Blocking的Socket</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setnonblocking</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(fd); </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 创建事件消息队列</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">epfd </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> epoll_create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(max_events);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 对队列做设置和管理</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">epoll_ctl</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..) </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 轮询epoll文件描述符</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  epoll_wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(..)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // process events</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><p>因此，为了实时接收数据变化，即使底层用NonBlocking+I/O多路复用，也<strong>无法避免轮询</strong>。而用这个技术的好处在于：<strong>复用了底层的轮询，上层被抽象成事件模型，在I/O线程的&quot;while true&quot;里干掉了所有非阻塞的事，避免大量线程都在轮询带来的Context Switch白耗CPU</strong>。在这一点上高性能的网络相关框架和组件：Netty、NodeJS、Nginx、Redis 等等异曲同工。</p>
<h2 id="从轮询到事件驱动" tabindex="-1">从轮询到事件驱动 <a class="header-anchor" href="#从轮询到事件驱动" aria-label="Permalink to &quot;从轮询到事件驱动&quot;">&ZeroWidthSpace;</a></h2>
<p>通过上面前端和后端的例子，我们发现最优解都是将<strong>事件队列的轮询藏在底层，把事件驱动模型提供给应用层</strong>，以此达到高效地实时通知数据变化的效果。底层轮询事件消息队列的实现，某种意义上充当了发布订阅模式中<strong>Broker</strong>的角色。</p>
<p><strong>如果我们把这种模式贯穿整个软件系统的设计和实现中会如何呢</strong>？</p>
<p>传统的软件设计和开发，大多使用的<strong>CRUD模式</strong>，把<strong>业务抽象成对资源的增删改查</strong>。所以很多程序员自嘲是增删改查工程师。</p>
<p>如果换用事件驱动的思维，发现还有另一种模式——CQRS。<strong>CQRS</strong>全称是&quot;Command Query Responsibility Segregation&quot;，翻译过来是&quot;命令查询职责分离&quot;，但不仅是读和写的分离，实际上CQRS往往搭配事件溯源（Event Sourcing）一起使用，以<strong>动态视角</strong>看待业务，这一点与静态视角的CRUD完全不同。</p>
<h4 id="crud模式的本质" tabindex="-1">CRUD模式的本质 <a class="header-anchor" href="#crud模式的本质" aria-label="Permalink to &quot;CRUD模式的本质&quot;">&ZeroWidthSpace;</a></h4>
<p>要理解CQRS模式，我们先思考CRUD模式本质是什么。</p>
<p>CRUD代表的四个动作：Create Read Update Delete，其中有三个是变化相关的：Create Update Delete，而这三个动作抽象的是<strong>事物从生到死</strong>的整个生命周期。比如一个用户被创建出来、用户的信息随着时间推移发生变化、最终由于某些原因被手动或自动的删除掉。</p>
<p>而Read代表对事物<strong>当前状态的观测</strong>，观测的结果就是软件的业务价值，这个结果传达的<strong>信息</strong>可以影响后续人或事的发展。<strong>更高阶的Read</strong>，比如对结果数据施以大数据分析或机器学习手段，得出从海量信息中提取出的<strong>模型和规律</strong>，是信息进化到更高维度的表现——&quot;知识&quot;，知识能够预测未来的趋势、指导未来的行为。</p>
<p>既然&quot;CUD&quot;是对事物变化的抽象，为什么说CRUD模型是静态视角呢？因为每个动作都相对独立，并没有体现当&quot;CUD&quot;发生时，又会<strong>对其他事物产生什么样的影响</strong>。我们极力避免&quot;耦合&quot;，但世间万物一定是彼此相关联的。软件代表事物在计算机中的模拟，那么当模拟的事物复杂到一定程度时，互相影响是必然的。当<strong>事物互相影响的复杂度大于事物自身生命周期维护的复杂度</strong>时，CRUD就体现出其局限性了。我们用各种RPC调用来互相通知产生的影响、或用消息队列到处传播自身的变化，最终整个软件系统变地<strong>嘈杂喧嚣</strong>。</p>
<h4 id="cqrs模式带来了什么" tabindex="-1">CQRS模式带来了什么 <a class="header-anchor" href="#cqrs模式带来了什么" aria-label="Permalink to &quot;CQRS模式带来了什么&quot;">&ZeroWidthSpace;</a></h4>
<p>CQRS的第一层意思：命令与查询的分离。CRUD模式中的Create、Update、Delete都可以归类为&quot;命令（Command）&quot;，与查询（Query）操作<strong>完全隔离</strong>。</p>
<p>这里的隔离<strong>不同于</strong>CRUD模型实现时的<strong>读写分离</strong>：将数据库从库标识为读库，select语句通过手动的方式或者切面编程，转移到从库执行。</p>
<p>CQRS的查询指的是<strong>在单独的查询组件读取当前状态的数据视图</strong>，压根没有主库从库的概念，写操作与读操作所对应的数据库是<strong>异构</strong>的：<strong>写进去的是命令对应的事件；读出来的是经历N个事件之后，数据的当前状态视图</strong>。</p>
<p>CQRS一般是结合另一种机制，事件溯源（Event Sourcing），共同实现基于事件驱动的软件系统的（CRQS还有一种不太常用的State Based表现形式，这里不讨论）。系统<strong>并不直接写入、更新数据本身，而是不停地追加事件</strong>。从当前时间之前发生的所有事件，来推断数据的当前状态，而这个当前状态一般会存储在视图库中以提高查询效率。</p>
<p>因此，<strong>基于事件驱动避免了轮询和广播数据本身，系统内的通信仅仅是时时刻刻发生的事件</strong>，这与真实的世界是一致的，我们通常会因为时时刻刻遇到的事件来决定下一步做什么的，所以说CQRS是以动态视角来看待业务的，而不是CRUD模式关注单个Request-Response的静态视角。事件被底层的消息队列统一调度协调，各个子系统只需把感兴趣的事件追加到自己数据库中，并计算出最新的数据视图，如有必要再产生另一个事件。如果把所有事件根据时间戳排序，能够清晰的捋出整个系统任何一个时间点的状态，这就是Event Sourcing + CQRS的强大之处。</p>
<p>CQRS + Event Sourcing模式还有哪些好处呢？</p>
<ul>
<li>当查询的并发很高时，数据视图可以有多个副本进一步提升性能；</li>
<li>事件驱动进一步减轻了系统之间的耦合，更容易实现Serverless；</li>
<li>如果某个系统出现BUG，根据历史事件可以追溯出正确的值，修复脏数据甚至只需要删除当前数据视图；</li>
<li>事件驱动天然是可审计的，无需单独的审计日志模块，可以轻易查询出历史执行过的命令，以及命令导致的在各个子系统传播的事件；</li>
<li>事件驱动天然是响应式的，事件的传播和及时处理意味着准实时的变化通知到了各端，而事件的异步处理往往也会用函数响应式编程范式（Functional Reactive Programming）</li>
<li>更彻底的隔离了读和写，传统的写法很容易的find/get中不小心调了update，隐式的副作用往往会导致非常难查的BUG，一切基于命令和事件的思维，能够杜绝读写职责不清晰带来的不确定的副作用。</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文从获取数据变化最简单的轮询方式出发，再分析前后端用来获取实时变化的事件驱动模式，简述了事件驱动模式底层的核心机制——轮询事件队列。当我们把可复用的轮询藏到底层时，上层就抽象出了一个强大的<strong>事件驱动模型</strong>，将该模型应用到软件系统的设计层面，又发现了不同于传统CRUD模式的<strong>CQRS模式</strong>。</p>
<p>虽然CQRS和Event Sourcing有很多好处，但当业务不那么复杂时，CRUD可能仍是更好的方案。CQRS也不是银弹：更高的心智成本、目前没有特别成熟的框架、作为事件中心的消息队列可能成为瓶颈、事件消息的传播途径也会变得极其复杂（甚至从有向无环图变成有环图产生其他问题）、追加写入的事件库可能变得非常庞大、适合CQRS模式的Saga分布式事务更加复杂等等。</p>
<p>没有最好的设计，只有更适合的设计。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——开闭原则]]></title>
            <link>https://code2life.top/blog/0053-ocp</link>
            <guid>https://code2life.top/blog/0053-ocp</guid>
            <pubDate>Tue, 11 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——开闭原则" tabindex="-1">软件设计杂谈——开闭原则 <a class="header-anchor" href="#软件设计杂谈——开闭原则" aria-label="Permalink to &quot;软件设计杂谈——开闭原则&quot;">&ZeroWidthSpace;</a></h1>
<p>如何在老代码上添加新的功能？这是个对程序猿来说最常见不过的问题了，一般有两个思路来应对。</p>
<ul>
<li>直接在老代码上一通魔改，实现新的功能。</li>
<li>老的不动，把原来的代码像三明治一样夹起来，在外层实现新功能，实在做不到再改老代码。</li>
</ul>
<h2 id="开闭原则" tabindex="-1">开闭原则 <a class="header-anchor" href="#开闭原则" aria-label="Permalink to &quot;开闭原则&quot;">&ZeroWidthSpace;</a></h2>
<p>这两种思路都很简单粗暴，但第一种想法是不太正确的，第二种是更好的。为什么要保持原代码尽量少改呢？</p>
<p>软件工程中有一个非常重要的原则——开闭原则：<strong>对扩展开放，对修改关闭</strong>。</p>
<p>开闭原则也是OOP理论中5个原则之一（分别是S O L I D，单一职责原则、开闭原则、里氏替换、接口隔离、依赖反转），是前辈们总结出的软件开发工程化的经验。</p>
<p>简单类比一下：要建一堵白墙，不是应该想方设法把砖头做成白色的，而是应该在砖墙外刷一层腻子粉。不然下次想它变成蓝色的墙，还要把墙砸了换蓝色的砖头，如果用添加封装层的思维，只需要再刷一层蓝色的乳胶漆就可以了。</p>
<h2 id="代码腐化之谜" tabindex="-1">代码腐化之谜 <a class="header-anchor" href="#代码腐化之谜" aria-label="Permalink to &quot;代码腐化之谜&quot;">&ZeroWidthSpace;</a></h2>
<p>曾有一篇文章写<a href="https://www.infoq.cn/article/cjz-architecture-corruption/" target="_blank" rel="noreferrer">架构是如何腐化</a>的，其实代码亦然，代码的腐化最终表现为架构的腐化。</p>
<p>上节说的第一种直接魔改老代码的思路，是有很多危害的，比如一通魔改让原来的函数产生了其他副作用的话，很容易引入BUG，修复BUG时又有概率引入新的BUG，而一般修BUG的代码都不会优雅。函数式编程推崇<strong>无状态无副作用的纯函数</strong>的组合来实现功能，其中一方面的原因在于，不可变（Immutable ）和显式（Explicit）的数据和逻辑更加健壮，即使出现问题也更容易定位。</p>
<p>然而，很多时候第一种方式更省事，短期来看又不会有什么大问题，大部分程序猿都不会多费一些脑子想怎么用第二种思路做，于是日积月累，代码中的坏味道就越发明显，留下的<strong>技术债</strong>越来越多。“债务”这种东西时间越久威力越大。最终，软件项目变成了传说中没有人愿意维护的shi山。</p>
<p><strong>千里之堤，溃于蚁穴</strong>。</p>
<h2 id="减缓代码腐化之道" tabindex="-1">减缓代码腐化之道 <a class="header-anchor" href="#减缓代码腐化之道" aria-label="Permalink to &quot;减缓代码腐化之道&quot;">&ZeroWidthSpace;</a></h2>
<p>软件也有生命周期，像人一样会“生老病死”，热力学第二定律告诉我们<strong>一切事物都会往熵增的方向发展</strong>，只有引入“负熵”才能维持局部系统的持续稳定。而软件系统中可以引入的“负熵”，一方面是<strong>重构</strong>，持续重构现有的代码实现和整体架构；另一方面，在实现新的需求时，遵循<strong>开闭原则</strong>避免老代码的加速腐化。</p>
<p>具体来看，在修改代码时第二种符合开闭原则的思路（暂且称之为&quot;夹三明治法&quot;），在实现时有不同的表现形式：</p>
<ul>
<li>修改参数时：利用<strong>函数重载</strong>或类似的机制，达到无需修改原代码调用方的目的；</li>
<li>增强功能时：利用<strong>多态性</strong>或合适的<strong>设计模式</strong>来设计编写新的代码，达到基本不用改原有代码的目的。</li>
</ul>
<p>不少设计模式的目的之一就是为了<strong>优雅地隔离出可能变化</strong>的地方，防止我们在变化发生时改动原有代码的，比如：代理模式，装饰者模式，职责链模式，策略模式，适配器模式等等都属于这一类。</p>
<p>其中代理模式的变种——AOP，在实际应用中很广泛，本质上也是这种“夹三明治”的思维方式。</p>
<p>如果添加的功能是与老代码关系不大的功能，又不想每个调用的地方都改，比如监控统计方法执行时间，添加数据库事务控制等等。这时“三明治”的外层就可以起个名字叫“面向切面编程（AOP）”了。像Java提供了注解和反射等机制，搭配JDK或Cglib的动态代理能力，可以实现在类似三明治模式的切面编程。而其他的语言，只要函数是&quot;一等公民&quot;，实现AOP是非常轻松的，比如下面这个最简化的JavaScript的例子，可以在不动原有的函数 a() 的情况下，给所有调用 a 的地方无感添加额外的逻辑，但这样用时最好不要在切面中引入影响原函数的副作用。</p>
<div class="language-javascript vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* something */</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> _a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // do other things</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _a.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h2 id="开闭原则的局限性" tabindex="-1">开闭原则的局限性 <a class="header-anchor" href="#开闭原则的局限性" aria-label="Permalink to &quot;开闭原则的局限性&quot;">&ZeroWidthSpace;</a></h2>
<p>在单体系统中，即使一直遵循开闭原则，一直重构现有的实现，也难以保障在越来越复杂的需求、不断扩大的团队中仍然可以减缓代码的腐化。</p>
<p>大型单体软件就像大型动物，维持稳定状态需要的负熵远大于小型动物。康威定律告诉我们，软件的结构与团队组织结构应当是一致的。当团队随着软件的复杂化而扩大时，将大型单体软件拆分成独立的小型子系统，每个小团队维护一个小型子系统，维持每个子系统的稳定所需要的“负熵”相对较小，即使总体上需要的“负熵”更多了，但沟通成本更低，小团队协作起来更加容易。这与上一篇提到的Scale-Cube的Y轴是一个意思，而单体系统的拆分要怎么进行下去，又是一个很庞大的话题，以后再写吧。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[软件设计杂谈——可伸缩性]]></title>
            <link>https://code2life.top/blog/0052-scalability</link>
            <guid>https://code2life.top/blog/0052-scalability</guid>
            <pubDate>Mon, 10 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="软件设计杂谈——可伸缩性" tabindex="-1">软件设计杂谈——可伸缩性 <a class="header-anchor" href="#软件设计杂谈——可伸缩性" aria-label="Permalink to &quot;软件设计杂谈——可伸缩性&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>当后端服务并发高到一定程度时非常考验软件架构设计，除了尽可能提升单机性能外，拥有强大的可伸缩性（Scalability）至关重要。如何提升软件系统的Scalability呢？</p>
<p>最简单的思路是水平扩展：1个撑不住就来10个，10个撑不住就来100个，100个不行就1000个。</p>
<p>这个思路看似没什么问题，然而真正实现起来却很难，我们再深入探究下去。</p>
<h2 id="从有状态组件的剥离谈起" tabindex="-1">从有状态组件的剥离谈起 <a class="header-anchor" href="#从有状态组件的剥离谈起" aria-label="Permalink to &quot;从有状态组件的剥离谈起&quot;">&ZeroWidthSpace;</a></h2>
<p>水平扩展的难点在于有状态的服务是很难水平扩展的，而整个系统的<strong>瓶颈</strong>往往会出现在有状态的组件上，比如数据库和缓存服务。</p>
<p>因此对于整个后端分布式系统，要实现水平扩展能力，首先要把<strong>有状态的组件拆分出来或者添加代理原入口的路由层</strong>，这样水平扩展出来的N个实例之间才具有可替代性。</p>
<p>以典型的状态数据：登录Session为例。如果把Session简单存储在服务端内存中，连水平扩展成两个实例都做不到，可以用下面这几个办法来解决：</p>
<ul>
<li>把Session从内存移到Redis缓存中，可以让服务端自身变成无状态的，但Session这个“状态”其实转移到Redis中了，如果并发高到一定程度还是要解决Redis的瓶颈；</li>
<li>把Session干掉，客户端登录后换取无状态但有过期时间的令牌，令牌拿到任何一个实例中都可以验证，常见的实现有JWT，确切的说是JWT中的JSON Web Signature (JWS)；</li>
<li>如果采用了方法1，要缓解Redis可能的瓶颈，更进一步的办法是对用户的Session数据进行人为或自动的拆分，或者说分片（Sharding），比如下面两种做法：
<ul>
<li>人为拆分数据：找某个用户数据的属性来人为分片，以省份为例，不同省份的用户登录进入不同省的服务器，这样并发就少了一个数量级。但是这又带来了新的问题，跨省查询就麻烦了，而且有的省人多有的省人少，存在资源利用不均的负载均衡问题；</li>
<li>自动拆分数据：可以用Redis Cluster，而不是用传统的主从加哨兵模式，Redis Cluster总共16384个Key的槽位，会分配到N组Redis Master中，存取数据时根据Key的CRC16值自动计算所落槽位，以此判断谁来处理客户端请求。这样一方面对数据做了分片，另一方面更多的Redis实例能支撑更多的客户端连接。如果新增或去除一组Redis Master/Slave，通过简单的运维操作，可以自动进行槽位的重新分配和数据迁移，这种动态的拆分数据的方法比上面人工的Sharding看上去更好一些，但仍然可能存在热点Key问题。总的来说瓶颈进一步被缓解了，能够支持服务端进一步水平扩展。</li>
</ul>
</li>
</ul>
<p>通过这个实际例子，可以发现仅仅是Session这样一个看似简单的问题，还没有涉及更难解决的瓶颈（比如<strong>实时通信层的有状态连接管理</strong>，<strong>存储层的分布式数据库</strong>等等），就足以掣肘整个系统的可伸缩性。</p>
<h2 id="可伸缩性的三个维度" tabindex="-1">可伸缩性的三个维度 <a class="header-anchor" href="#可伸缩性的三个维度" aria-label="Permalink to &quot;可伸缩性的三个维度&quot;">&ZeroWidthSpace;</a></h2>
<p>回到理论层面，如何才能做到近乎无穷的伸缩性呢？下面这张“Scale Cube”来自Marty Abbott的《The Art of Scalability》</p>
<p><img src="//filecdn.code2life.top/scale-cube.jpg" alt=""></p>
<p>图中有三个正交的维度，需要对每个维度都施以对应的方案，才能做到整个系统的可伸缩。</p>
<ul>
<li><strong>X轴</strong>，把1个实例变成N个实例，也就是最初的思路，无状态服务可以依靠基础设施平台的能力实现理论上无限的扩容，再加上负载均衡层来分配每个实例干的活；</li>
<li><strong>Y轴</strong>，把单体系统变成N个子系统，需要构建领域模型，解构分割出耦合相对较小的多个服务，再以此将单体业务系统拆分落地成微服务系统；</li>
<li><strong>Z轴</strong>，把整体数据变成N块数据，用数据分区分片的技术拆分数据，或者部署N个独立完整的系统处理不同区域的业务，来缓解“有状态数据”剧增的瓶颈。</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>可伸缩性的三个维度，X，Y，Z 每个轴做好的难度都是<strong>上不封顶</strong>，下面这张图是AWS和Netflix多年前通过链路追踪画出来的微服务依赖图。</p>
<p><img src="//filecdn.code2life.top/aws-netflix.jpg" alt=""></p>
<p>这样怪物一般复杂系统的形成，也源自同样的思维方式和理论。曾见过阿里的监控平台画出来的链路图，比Amazon有过之而无不及，这么大体量高并发的复杂系统，对X轴在基础设施平台的要求，Y轴在架构设计的要求，Z轴在分布式中间件和存储组件的要求都是极高的，在这些方向上做到极致的人和团队是非常值得敬佩的。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Prometheus监控告警——基础篇]]></title>
            <link>https://code2life.top/blog/0048-prometheus-in-action-start</link>
            <guid>https://code2life.top/blog/0048-prometheus-in-action-start</guid>
            <pubDate>Fri, 28 Feb 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-prometheus监控告警——基础篇" tabindex="-1">[DevOps] Prometheus监控告警——基础篇 <a class="header-anchor" href="#devops-prometheus监控告警——基础篇" aria-label="Permalink to &quot;[DevOps] Prometheus监控告警——基础篇&quot;">&ZeroWidthSpace;</a></h1>
<p>最近两年做DevOps相关工作时，学了一些Prometheus监控告警系统的知识并规模化实践落地，这个系列分享一些关于Prometheus的技术干货。</p>
<h2 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="/blog/0048-prometheus-in-action-start.html">Prometheus监控告警——基础篇</a></li>
<li><a href="/blog/0049-prometheus-in-action-usage.html">Prometheus监控告警——实战篇</a></li>
<li><a href="/blog/0050-prometheus-in-action-impl.html">Prometheus监控告警——原理篇</a></li>
<li><a href="/blog/0051-prometheus-in-action-thinking.html">Prometheus监控告警——总结与思考</a></li>
</ul>
<h3 id="what-什么是prometheus" tabindex="-1">What, 什么是Prometheus？ <a class="header-anchor" href="#what-什么是prometheus" aria-label="Permalink to &quot;What, 什么是Prometheus？&quot;">&ZeroWidthSpace;</a></h3>
<p>Prometheus是继Kubernetes之后，<strong>第二个从云原生计算基金会（CNCF）毕业的项目</strong>。<strong>Prometheus是Google监控系统BorgMon类似实现的开源版</strong>，整套系统由<strong>监控服务、告警服务、时序数据库等几个部分，及周边生态的各种指标收集器（Exporter）组成</strong>，是在当下主流的<strong>云原生</strong>监控告警系统，Prometheus有这些特性：</p>
<ul>
<li>开箱即用的各种服务发现机制，可以<strong>自动发现监控端点</strong>；</li>
<li>专为监控指标数据设计的<strong>高性能时序数据库TSDB</strong>；</li>
<li>强大易用的查询语言<strong>PromQL</strong>以及丰富的<strong>聚合函数</strong>；</li>
<li>可以配置灵活的告警规则，支持<strong>告警收敛（分组、抑制、静默）、多级路由</strong>等等高级功能；</li>
<li><strong>生态完善</strong>，有各种现成的开源Exporter实现，实现自定义的监控指标也非常简单。</li>
</ul>
<h3 id="why-为什么需要prometheus" tabindex="-1">Why, 为什么需要Prometheus？ <a class="header-anchor" href="#why-为什么需要prometheus" aria-label="Permalink to &quot;Why, 为什么需要Prometheus？&quot;">&ZeroWidthSpace;</a></h3>
<p>Prometheus和Kubernetes有很多相通之处，<strong>Kubernetes其中一个功能是提供了弹性动态的部署能力</strong>，而<strong>Prometheus则提供了动态的监控能力</strong>。Kubernetes已经成为实事标准，与之相辅相成的Prometheus自然也成为了云原生监控告警的首选项。</p>
<p>举个例子：Kubernetes集群里新加了X台机器，Y个服务横向扩容了Z个新的实例，此时Prometheus监控系统的配置需要怎么修改？这个问题是多余的，<strong>不需要任何配置修改</strong>！</p>
<p>Kubernetes集群的扩容可以自动化是因为所有的<strong>资源对象，调度策略</strong>都已经在<strong>标准化的编排文件中声明</strong>了，搭配云服务器的自适应扩缩容以及Kubernetes提供的Horizontal Pod AutoScaler等机制，可以获得<strong>恐怖的弹性能力和运维自动化水平</strong>。这对<strong>传统运维</strong>（手工执行命令，或工具批量执行脚本）是无法想象的冲击和变革。同样，<strong>传统的监控方案如Zabbix</strong>，使用批量执行的脚本在机器中安装agent、agent上传数据给监控服务器这种模式，也会因为无法适应这种<strong>动态性</strong>而逐渐被<strong>淘汰</strong>，而Prometheus这种能支持<strong>动态部署拓扑</strong>的监控系统成为主流。</p>
<p>上一个小节提到的特性都是Prometheus的优点，如果说缺点，比较有争议的两个：</p>
<ul>
<li>通过HTTP拉取监控数据效率不够高；</li>
<li>没有任何监控告警之外的功能（用户/角色/权限控制等等）。</li>
</ul>
<p>第一个HTTP拉监控数据的效率问题对于<strong>绝大多数场景碰不到</strong>；第二个问题可以通过二次开发，或者把Prometheus完全作为后端内部服务只暴露Grafana这样具有更强管理和可视化功能的前/中端服务来解决。综上，Prometheus非常适合构建云原生环境的监控平台。</p>
<h3 id="how-如何快速使用prometheus" tabindex="-1">How, 如何快速使用Prometheus？ <a class="header-anchor" href="#how-如何快速使用prometheus" aria-label="Permalink to &quot;How, 如何快速使用Prometheus？&quot;">&ZeroWidthSpace;</a></h3>
<p>安利完了，下面说一下基本使用。官方的<a href="https://prometheus.io/docs/prometheus/latest/getting_started/" target="_blank" rel="noreferrer">Getting Started教程</a>是从二进制文件直接运行开始的，我们来稍微云原生一点的方式来入门：用Docker容器化运行。</p>
<p>首先，Prometheus Server和AlertManager两个容器都需要各外挂两个Volume，一个存储持久化数据的目录，另一个是配置文件，最简单的配置如下（示例配置，分别放置在 /opt/prometheus/prometheus.yml, /opt/prometheus/alertmanager.yml 两个路径）。</p>
<p>prometheus.yml</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">global</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 默认每隔30秒执行一次表达式判断告警规则</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  evaluation_interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">30s</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 默认每隔30秒向各个监控端点拉取一次指标数据</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  scrape_interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">30s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">rule_files</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 表达式计算和记录，告警规则等自定义的配置文件</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/etc/prometheus-data/rules/*.yaml</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">scrape_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 最关键的地方，配置1-N个需要监控的端点，这里配置的是最简单的静态规则，直接从配置的地址抓取监控数据</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">job_name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'prometheus'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    static_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # 两个监控的端点，9090是Prometheus自己，9100是机器的监控组件Node Exporter</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">targets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'127.0.0.1:9090'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'127.0.0.1:9100'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">alerting</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  alert_relabel_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">action</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">labeldrop</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    regex</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">prometheus_replica</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 配置AlertManager的地址，若触发rules中的告警规则，调用AlertManager的接口发送原始告警数据</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  alertmanagers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">path_prefix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    scheme</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">http</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # AlertManager地址也是用的静态配置，本地的9093端口</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    static_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">targets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">127.0.0.1:9093</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>alertmanager.yml</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">global</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  resolve_timeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">5m</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 发送告警的邮箱账号配置</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  smtp_from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">from@example.com</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  smtp_smarthost</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">smtp.example.com:587</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  smtp_auth_username</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">from@example.com</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  smtp_auth_password</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your_email_password</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  smtp_require_tls</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 告警的路由规则，支持多级路由和收敛功能，具体参数含义后面再讲</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  group_by</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'job'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  group_wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">15s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  group_interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">5m</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  repeat_interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">12h</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 默认的告警接收方式</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  receiver</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">webhook</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  routes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 匹配到特定条件后，指定使用某种告警接收方式</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">match_re</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      severity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">warning|error|critical</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    receiver</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">email</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 告警接收者的配置</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">receivers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">webhook</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  webhook_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # webhook测试有个神奇的网站，可以打开 https://webhook.site，</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 将自动生成的临时链接粘到下面，如果触发告警可以在网站上看到</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 详细的请求内容 （国内访问可能稍微慢一些）</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">https://webhook.site/{random-session-id}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">email</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  email_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">to</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-email@example.com,op-team@example.com</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br></div></div><p>再分别创建PrometheusServer，AlertManager，Grafana的数据目录 /data/prometheus, /data/alertmanager, /data/grafana （这些目录文件的路径与docker run -v参数冒号前的路径一致即可），确保<strong>目录读写权限</strong>没有问题之后，直接docker run启动如下4个容器：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Prometheus Server &#x26; TSDB</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --net</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /opt/prometheus/:/etc/prometheus:ro</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /data/prometheus:/etc/prometheus-data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prometheus</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> always</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prom/prometheus</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --config.file=/etc/prometheus/prometheus.yml</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --storage.tsdb.path=/etc/prometheus-data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --storage.tsdb.retention.time=30d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --web.enable-lifecycle</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --storage.tsdb.no-lockfile</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --web.route-prefix=/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># AlertManager</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --net</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /opt/alertmanager/:/etc/alertmanager/config:ro</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /data/alertmanager:/etc/alertmanager</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> alert</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> always</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prom/alertmanager</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --config.file=/etc/alertmanager/config/alertmanager.yml</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --storage.path=/etc/alertmanager</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --data.retention=168h</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --web.listen-address=:9093</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --web.route-prefix=/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Node Exporter</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> node-exporter</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> always</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --net</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prom/node-exporter</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Grafana  （-e设置第一次启动的admin初始密码的环境变量）</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> always</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --net</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> host</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name=grafana</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "GF_SECURITY_ADMIN_PASSWORD=your-admin-password"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /data/grafana:/var/lib/grafana</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> grafana/grafana</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 稍等片刻， 执行 docker ps 看下是否都成功启动了</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如果失败使用 docker logs alert / docker logs prometheus 查看日志</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如有写权限问题执行 chmod/chown 命令更改数据目录的写权限</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><p>注：这里使用<strong>Host Network容器并不是一个好的实践</strong>，只是方便入门，<strong>屏蔽了容器网络互通的复杂技术细节</strong>。一般直接跑Docker容器也比较少见，后面的文章会陆续讲解如何用Prometheus Operator在Kubernetes集群中搭建<strong>生产环境级别</strong>的监控告警系统，以及如何实现<strong>高可用架构</strong>。</p>
<p>运行成功之后，在localhost的<strong>9090端口是Prometheus Server，9093端口是AlertManager，9100端口是Node Exporter，3000端口是Grafana</strong>。浏览器打开即可自行探索。打开 <a href="//127.0.0.1:9090" target="_blank" rel="noreferrer">//127.0.0.1:9090</a>，默认的/graph就是监控指标数据的查询界面，在Status下有一些Prometheus的服务器信息，比如 /targets 可以看到已经在监控的两个端点，如下图。</p>
<p><img src="//filecdn.code2life.top/prom-targets.png" alt=""></p>
<p>再打开 <a href="//127.0.0.1:3000" target="_blank" rel="noreferrer">//127.0.0.1:3000</a> 登录到Grafana中（第一次需要修改初始密码），在左侧选取<strong>Configuration-&gt;Data Sources-&gt;Add DataSource-&gt;Prometheus</strong>添加本地的Prometheus数据源。</p>
<p><img src="//filecdn.code2life.top/grafana.png" alt=""></p>
<p>最后在左侧点Dashboards-&gt;Manage-&gt;Import**，导入ID为<strong>1860</strong>的Node Exporter Dashboard （也可以到Grafana Labs上搜索其他的Dashboard），即可看到上百项主机监控图表了。</p>
<p><img src="//filecdn.code2life.top/grafana-ne.png" alt=""></p>
<h3 id="prometheus的核心数据流" tabindex="-1">Prometheus的核心数据流 <a class="header-anchor" href="#prometheus的核心数据流" aria-label="Permalink to &quot;Prometheus的核心数据流&quot;">&ZeroWidthSpace;</a></h3>
<p>经过上面的部署运行，我们对Prometheus的几个关键组件：<strong>Prometheus (TSDB + Server)，AlertManager，Exporters</strong>，以及周边组件<strong>Grafana</strong>，有了直观的认知。那么，这些数据是怎么来的呢？又是如何被处理和存储的呢？如果触发告警，告警是如何发送呢？</p>
<p>简化的数据流大概是这样的：</p>
<ul>
<li>Prometheus Server启动后读取配置，解析服务发现规则（xxx_sd_configs）<strong>自动收集需要监控的端点</strong>，而上述例子中的<strong>静态监控端点</strong>（static_configs）是最基础的写死的形式，而在Kubernetes中大多使用 <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config" target="_blank" rel="noreferrer">kubernetes_sd_config</a>。</li>
<li>Prometheus Server <strong>周期&quot;刮取&quot;</strong>（scrape_interval）监控端点的HTTP接口数据。</li>
<li>Prometheus Server HTTP请求到达Node Exporter，Exporters返回一个文本响应，每个非注释行包含一条完整的时序数据：<strong>监控名称 + 一组标签键值对 + 样本数据</strong>。例如：node_cpu_seconds_total{cpu=&quot;0&quot;,mode=&quot;idle&quot;} 3320.88。</li>
<li>Prometheus Server收到响应，Relabel处理之后（relabel_configs），存储到TSDB文件中，根据Label建立<strong>倒排索引</strong>（Inverted Index）。</li>
<li>Prometheus Server 另一个<strong>周期计算</strong>任务（周期是 evaluation_interval）开始执行，根据配置的Rules的表达式逐个计算，若<strong>结果超过阈值并持续时长超过临界点，发送Alert到AlertManager</strong>，下面会说Rule具体是什么样子的。</li>
<li>AlertManager 收到原始告警请求，根据<strong>配置的策略决定是否需要触发</strong>告警，如需告警则根据配置的<strong>路由链路依次发送告警</strong>，比如邮件，企业微信，Slack，PagerDuty，通用的WebHook等等。</li>
<li>当通过界面或来自其他系统（比如Grafana）的HTTP调用<strong>查询时序数据</strong>时，传入一个<strong>PromQL表达式</strong>，Prometheus Server处理过滤完之后，返回三种类型的数据：<strong>瞬时向量</strong> ，<strong>区间向量</strong> ，或<strong>标量数据</strong> （还有一个字符串类型，但目前没有用）。</li>
</ul>
<p>对于这些数据类型的概念，具体解释如下：</p>
<ul>
<li><strong>样本数据（Sample）</strong>：是一个<strong>浮点数</strong>和一个<strong>时间戳</strong>构成的时序数据基本单位；</li>
<li><strong>瞬时向量（Instant Vector）</strong>：某一时刻, 0-N条只有一个Sample的时序数据，直接查询某个指标的当前值，或用offset查询之前某个时间点的指标值，就是瞬时向量，比如：查询3分钟之前那个时间点，每个硬盘挂载点的可用空间， node_filesystem_avail_bytes offset 3m，3个挂载点则返回3条数据；</li>
<li><strong>区间向量（Range Vector）</strong>：一段时间范围内，0-N条包含M个不同时刻Sample的时序数据，比如：查询最近3分钟内，每隔30s取样的所有硬盘可用空间，node_filesystem_avail_bytes[3m]，3个盘则返回3×6=18条数据；</li>
<li><strong>标量数据（Scalar）</strong>： 一个浮点数，可能是计算出来的单一结果，或是一个单纯的常量，不带任何指标或标签信息。</li>
</ul>
<p>下图是一个典型的区间向量的查询结果（node_cpu_seconds_total{mode=&quot;iowait&quot;}[2m] 当前2分钟内CPU处于iowait的耗时），可见每一行的Value都有4个值，也就是2分钟抓取到的4个该指标样本数据。</p>
<p><img src="//filecdn.code2life.top/range-vector.png" alt=""></p>
<p>整个流程的具体细节后面讲原理的时候再深入。其中<strong>最关键</strong>的点在于：<strong>Prometheus是主动发现需要监控的Endpoints，然后主动轮询拉取监控数据，Pull模型代替了传统的Push模型</strong>。这种<strong>颠覆性</strong>的设计，让<strong>中心化的Prometheus Server集中配置如何去发现需要监控的东西</strong>，相对于传统agent上传的模式更具有灵活性和动态扩展能力。</p>
<p>注：虽然Prometheus的组件还有一个<strong>Push Gateway</strong>，用来让传统的Agent上传数据，但最终也是<strong>收集并转换成HTTP端点</strong>让Prometheus刮取，Push Gateway常用于监控Job类的组件或衔接遗留系统，本系列不作展开。</p>
<h3 id="promql语法简介及示例" tabindex="-1">PromQL语法简介及示例 <a class="header-anchor" href="#promql语法简介及示例" aria-label="Permalink to &quot;PromQL语法简介及示例&quot;">&ZeroWidthSpace;</a></h3>
<p>在上面运行的Prometheus Server的Web页面中，直接输入指标名称就可以得到一条指标的瞬时向量，但我们实际上经常要查询复杂的聚合数据，要怎么写查询语句呢？</p>
<p>这时就该PromQL上场了，相比于类似SQL语法的InfluxDB的查询语言，PromQL更加简单易用，直接输入指标名称就是PromQL最基础的写法。除了看Prometheus的文档： <a href="https://prometheus.io/docs/prometheus/latest/querying/basics/" target="_blank" rel="noreferrer">https://prometheus.io/docs/prometheus/latest/querying/basics/</a>，学习PromQL还有个办法，就是直接在Grafana中看那些开源的Dashboard是怎么写的。</p>
<p>因为一些PromQL的函数只能应用在特定的指标类型上的，在讲PromQL之前我们先了解一下Prometheus的<strong>指标有哪些类型</strong>。Prometheus提供了<a href="https://prometheus.io/docs/concepts/metric_types/" target="_blank" rel="noreferrer">4种数据类型</a>：<strong>Counter Gauge Summary Histogram</strong>，指标类型在指标数据上方的注释中（#TYPE）。</p>
<ul>
<li><strong>Counter</strong>用来表示递增的指标，可以reset归零，比如： process_cpu_seconds_total 47.82</li>
<li><strong>Gauge</strong>用来表示可增可减的指标，比如：go_goroutines 31</li>
<li><strong>Summary和Histogram</strong>用来更加精确的分位统计，都包含一个采样点的数量(count)和总值(sum)，区别在于Summary是客户端计算好固定分位的值，而Histogram柱状图只对每个桶做计数，需要Prometheus Server计算分位值。这两种数据类型不太容易理解，详细解释参考这篇文章：<a href="https://blog.csdn.net/wtan825/article/details/94616813" target="_blank" rel="noreferrer">https://blog.csdn.net/wtan825/article/details/94616813</a>。</li>
</ul>
<p>下面我们就从Grafana Dashboard中举几个常见的例子来说明PromQL的用法。</p>
<p><strong>基础题：如何计算磁盘已用空间所占百分比</strong>？</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>100 - (</span></span>
<span class="line"><span>  (node_filesystem_avail_bytes{instance="$node",device!~'rootfs'} * 100) / </span></span>
<span class="line"><span>  node_filesystem_size_bytes{instance="$node",device!~'rootfs'}</span></span>
<span class="line"><span>)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p><strong>解析</strong>：node_filesystem_avail_bytes和node_filesystem_size_bytes是Node Exporter的两个指标，&quot;{}&quot;中是按label查询的具体条件（$node是Grafana中的变量，会在调用查询时替换成真正的值），分别是磁盘可用空间和总空间，二者相除后乘100%，再拿100%减去该值，就是已用空间的比例。</p>
<p><strong>简单题：如何计算CPU在用户态运行所占百分比</strong>？</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>sum by (instance)</span></span>
<span class="line"><span>  (irate(node_cpu_seconds_total{mode="user",instance="$node",job="$job"}[2m])) * 100</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p><strong>解析</strong>： irate是常用的算区间向量变化速率的函数，因此irate括号中的指标加上&quot;[2m]&quot;表示从2分钟内的区间向量取样计算。但机器有很多核心，因此再加一个sum聚合函数，把每个instance的N个核心加到一起，类似于SQL的&quot;select sum(xx) from ... group by instance&quot;。sum是个使用频率很高的聚合操作，其他的<a href="https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators" target="_blank" rel="noreferrer">聚合操作函数</a>还有count min max avg topk bottomk group stddev等等。&quot;by (some_label)&quot; 放在前面和后面都可以，与&quot;by&quot;相反的有个&quot;without&quot;可以对labels集合取差集，比如这个例子也可以写成sum without (cpu,mode)。</p>
<p>注：</p>
<ul>
<li>这里的百分比不是严格意义的百分数，而是与linux的top命令一致的，总量是核数x100%，也就是说 8核CPU就是800%。</li>
<li><strong>irate、rate、increase、deriv、delta、idelta</strong>函数的区别：irate是对区间向量<strong>最后两个样本</strong>求斜率，rate是区间向量<strong>首尾两个样本</strong>求斜率，increase只算了Counter类型区间向量首尾相比增长量有多少而不除时间，deriv是最小二乘法拟合的线性回归斜率，delta作用于Gauge类型的指标算区间首尾差值，idelta算Gauge区间向量最后两个样本的差值。根据这些原理的差别在不同场景选用不同的函数，比如irate适用于像CPU占用时间这类<strong>易突变</strong>的Counter,rate适用于变化<strong>相对缓慢</strong>的Counter。</li>
</ul>
<p><strong>普通题： Nginx Ingress每个后端服务的P99请求延迟</strong>？</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>histogram_quantile(0.99, </span></span>
<span class="line"><span>  sum(</span></span>
<span class="line"><span>    rate(nginx_ingress_controller_request_duration_seconds_bucket{ingress=~"$ingress"}[2m])) </span></span>
<span class="line"><span>  by (le, ingress)</span></span>
<span class="line"><span>)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><strong>解析</strong>：这是Kubernetes中Nginx Ingress Dashboard中的例子，HTTP的响应延时被分到不同的bucket中分别计总响应时间，le就是区间的分割label，也是柱状图计算函数必要的label，调用histogram_quantile传入0.99表示计算前99%的HTTP请求的响应时间。</p>
<p><strong>进阶题： 预测硬盘在多少天之后存储空间会使用到95%</strong>?</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>(node_filesystem_avail_bytes / node_filesystem_size_bytes - 0.05) / </span></span>
<span class="line"><span>(</span></span>
<span class="line"><span>  ((predict_linear(node_filesystem_avail_bytes[6h], 3600*24) -</span></span>
<span class="line"><span>   node_filesystem_avail_bytes)) / node_filesystem_avail_bytes</span></span>
<span class="line"><span>)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><strong>解析</strong>：<strong>predict_linear</strong>函数通过线性回归模型预测某个指标在一段时间之后的值，因此我们计算一天内存储增长了百分之多少，再拿当前可用的空间百分比与95%的差值来做除数，就可以算出存储达到95%需要的天数了，这种预测对于数据库这类存储空间占用随业务稳步增长而增的服务非常有用，让我们可以知道何时需要对硬盘扩容。</p>
<p>通过这几个例子，对Prometheus的运算和函数有了直观的认识，结合官方文档多写一写练一练很快就能熟练使用了。另外，还有几个使用频率颇高的函数：</p>
<ul>
<li><strong>absent和count</strong>： absent判断服务是否还在，由于监控端点消失之后，Prometheus的视角是无法知道是正常消失还是异常退出，一般用absent或count来监控服务还在不在了，或者存在多少个部署实例，对服务健康度。</li>
<li><strong>resets和changes</strong>： resets判断是否发生单调性变化，计数器重置，比如判断是否发生了重启；changes判断值的变化次数。</li>
<li><strong>round/floor/ceil</strong>： 对结果做舍入，防止计算出来的小数点位数太长。</li>
<li><strong>{aggr}_over_the_time</strong>： 对区间向量先做一次聚合，变成瞬时向量，这里的聚合操作符&quot;{aggr}&quot;可以替换成任意一个Prometheus支持的<a href="https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators" target="_blank" rel="noreferrer">聚合操作</a>。下图是一个在Grafana上对比avg_over_time区间向量和直接查询瞬时向量的例子。</li>
</ul>
<p><img src="//filecdn.code2life.top/grafana_aggr_over_time.png" alt=""></p>
<h3 id="alert配置和集成" tabindex="-1">Alert配置和集成 <a class="header-anchor" href="#alert配置和集成" aria-label="Permalink to &quot;Alert配置和集成&quot;">&ZeroWidthSpace;</a></h3>
<p>通过上面对Prometheus数据流的概要理解，我们知道<strong>AlertManager组件只是对收到的Alert做处理，不管Alert是如何触发的，告警的计算和触发是在Prometheus Server</strong>。因此告警的配置也分两块：</p>
<ul>
<li>第一部分在Prometheus中，配置Rules决定原始Alert如何触发</li>
<li>另一部分在AlertManager中，配置Alert处理策略</li>
</ul>
<p>第一部分决定原始告警是否触发，是需要经常维护的；第二部分在AlertManager中的alertmanager.yml中，第一次配好后几乎不用修改。这种设计让<strong>动态配置和静态配置分离</strong>，更易于维护。</p>
<p>Prometheus配置的Yaml中有一行<strong>rule_files</strong>，Prometheus Server启动后会读取匹配该路径的所有文件，载入配置后每隔一段时间（evaluation_interval定义的时间）会逐个计算<strong>record和alert</strong>类型的Rule。</p>
<p><strong>record类型</strong>的rule是对指标进行二次计算后再存入TSDB方便以后直接查询；<strong>alert类型</strong>的rule会计算后判断是否超过阈值持续了一段时间，符合告警条件就调用AlertManager的API创建原始告警数据。</p>
<p>那么，这两种Prometheus Rule要怎么写呢？</p>
<p>Record Rule的典型的格式如下，包括一个表达式expr和一个新的指标名称record，下面的例子来自Prometheus Operator对Kubernetes中CPU使用率的统计。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">groups</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">k8s.rules</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  rules</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 根据 Kubernetes Namespace聚合，算出每个Namespace的总CPU占有率</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 记录到新的指标：“namespace:container_cpu_usage_seconds_total:sum_rate”</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">expr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])) by (namespace)</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    record</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">namespace:container_cpu_usage_seconds_total:sum_rate</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 根据 namespace, pod_name, container_name 三个label聚合，方便直接查询每个Container的CPU占用</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 记录到新的指标：“namespace_pod_name_container_name:container_cpu_usage_seconds_total:sum_rate”</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">expr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      sum by (namespace, pod_name, container_name) (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      )</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    record</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">namespace_pod_name_container_name:container_cpu_usage_seconds_total:sum_rate</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><p>Alert Rule的典型格式如下，包括annotation、expr、for、labels等几个部分。&quot;expr&quot;的条件如果满足，并持续了&quot;for&quot;定义的时长，就会根据&quot;annotation&quot;和&quot;labels&quot;定义的元数据生成告警数据。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">groups</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">prometheus-and-exporters</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  rules</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 告警名称</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ContainerRestartAlert</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 告警具体内容的模板字符串</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    annotations</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'{{ $labels.application }} restarted {{ $value }} times recently'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      summary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Application has been restarted ({{ $labels.application }})</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 条件表达式，满足条件还要再判断持续时长</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    expr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">resets(process_uptime_seconds[5m]) > 0</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 此例是如果发现容器重启(进程的uptime Counter发生了reset)，持续5s后立即发出告警</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">5s</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 给告警打label，严重性是比较常用的</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # AlertManager能够根据label匹配可以做告警分级通知</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      severity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">warning</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>常用的Alert Rules有个网站可以找到很多现成的写法：<a href="https://awesome-prometheus-alerts.grep.to/rules" target="_blank" rel="noreferrer">https://awesome-prometheus-alerts.grep.to/rules</a>，Copy-Paste即可避免重复造轮子了。</p>
<p>原始告警发送到Alert Manager之后，会按照上面alertmanager.yaml的配置，经过分组、抑制、静默等流程，最终<strong>收敛后的告警</strong>会路由到不同的<strong>通知方式</strong>。告警的通知方式有很多，除了官方支持的这些常用的邮件、IM等方式（<a href="https://prometheus.io/docs/operating/integrations/" target="_blank" rel="noreferrer">https://prometheus.io/docs/operating/integrations/</a>），也可以自行开发集成WebHook。我前年业余时间做了个集成Zoom客户端的Webhook转换组件，可以将<strong>告警直接推送到Zoom群聊中，一键点击开启TroubleShooting Meeting</strong>。链接如下：</p>
<p><a href="https://github.com/Code2Life/nodess-apps/tree/master/src/zoom-alert-2.0" target="_blank" rel="noreferrer">https://github.com/Code2Life/nodess-apps/tree/master/src/zoom-alert-2.0</a></p>
<p><img src="//filecdn.code2life.top/prm-alert-zoom.jpg" alt=""></p>
<h3 id="常用的exporter合集" tabindex="-1">常用的Exporter合集 <a class="header-anchor" href="#常用的exporter合集" aria-label="Permalink to &quot;常用的Exporter合集&quot;">&ZeroWidthSpace;</a></h3>
<p>我们了解了Prometheus的核心机制是根据xxx_sd_config来自动发现监控端点，这些监控端点就是Exporter提供的监控数据的HTTP接口，所以各种Exporters就是Prometheus监控数据的源头，有哪些常用的Exporter呢？</p>
<p>除了硬件指标收集的Node Exporter, Prometheus官方也提供了一些常用组件的监控指标Exporter，比如<strong>Mysql Exporter、Consul Exporter、JMX Exporter</strong>等等。在官方提供的Exporter之外，开源社区贡献了更多琳琅满目的组件和框架的各种Exporters，项目主页基本都有配置运行教程，绝大多数都是一行Docker命令或者一个Kubernetes Yaml文件，就可以轻松部署。</p>
<p>我们以在这里直接查询到大部分官方或开源社区提供的Exporters： <a href="https://prometheus.io/docs/instrumenting/exporters/" target="_blank" rel="noreferrer">https://prometheus.io/docs/instrumenting/exporters</a>。</p>
<p>如果是一些具体业务相关的监控指标，找不到满足需求的Exporter，在应用服务中自定义监控指标也不难，下篇我们再详细展开。</p>
<h3 id="grafana-dashboards" tabindex="-1">Grafana Dashboards <a class="header-anchor" href="#grafana-dashboards" aria-label="Permalink to &quot;Grafana Dashboards&quot;">&ZeroWidthSpace;</a></h3>
<p>Grafana是一个通用的<strong>时序数据的可视化平台</strong>，严格意义上并不属于Prometheus系统，因为它经常搭配Prometheus使用，基本的使用步骤已经讲过了，这里分享几个<a href="https://grafana.com/grafana/dashboards" target="_blank" rel="noreferrer">Grafana Labs</a>上常用的Dashboard，Dashboard翻译成看板、仪表盘都感觉怪怪的。</p>
<ul>
<li>Node Exporter Full(1860)，</li>
<li>JVM Micrometer(4701), Spring Boot Statistics(6756)</li>
<li>Golang Go Processes(6671)</li>
<li>Redis Dashboard(763)</li>
<li>MySQL Overview(7362)</li>
<li>Kubernetes Nginx Ingress Dashboard (<a href="https://github.com/kubernetes/ingress-nginx/tree/master/deploy/grafana/dashboards" target="_blank" rel="noreferrer">https://github.com/kubernetes/ingress-nginx/tree/master/deploy/grafana/dashboards</a>),</li>
</ul>
<p>如果要创建<strong>自定义的Dashboard</strong>，Grafana支持可视化编辑和代码自动补全，掌握PromQL就可以熟能生巧。我业余时间创建了三个Dashboard贡献到Grafana Labs了，分别是Node.js的Dashboard、阿里的Druid数据库连接池的Dashboard、Consul状态以及注册服务的Dashboard，其中Node.js的目前已经一千多下载量了，看来很多人也都有类似的需求，有需要可以直接导入或复制JSON后自行改造。</p>
<ul>
<li>NodeJS Application Dashboard (11159): <a href="https://grafana.com/grafana/dashboards/11159" target="_blank" rel="noreferrer">https://grafana.com/grafana/dashboards/11159</a></li>
<li>Druid Connection Pool Dashboard for SpringBoot (11157): <a href="https://grafana.com/grafana/dashboards/11157" target="_blank" rel="noreferrer">https://grafana.com/grafana/dashboards/11157</a></li>
<li>Consul Exporter Dashboard (12049): <a href="https://grafana.com/grafana/dashboards/12049" target="_blank" rel="noreferrer">https://grafana.com/grafana/dashboards/12049</a></li>
</ul>
<p><img src="//filecdn.code2life.top/nodejs-app-board.jpeg" alt=""></p>
<h2 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h2>
<p>现在我们对Prometheus以及周边组件的基本使用和数据流有了大概的了解，能够搭建最基础的监控告警系统了，但无法满足生产环境的要求，下一篇我们来讲解在生产环境中使用Prometheus的一些实战经验。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Prometheus监控告警——实战篇]]></title>
            <link>https://code2life.top/blog/0049-prometheus-in-action-usage</link>
            <guid>https://code2life.top/blog/0049-prometheus-in-action-usage</guid>
            <pubDate>Thu, 27 Feb 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-prometheus监控告警——实战篇" tabindex="-1">[DevOps] Prometheus监控告警——实战篇 <a class="header-anchor" href="#devops-prometheus监控告警——实战篇" aria-label="Permalink to &quot;[DevOps] Prometheus监控告警——实战篇&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><a href="/blog/0048-prometheus-in-action-start.html">Prometheus监控告警——基础篇</a></li>
<li><a href="/blog/0049-prometheus-in-action-usage.html">Prometheus监控告警——实战篇</a></li>
<li><a href="/blog/0050-prometheus-in-action-impl.html">Prometheus监控告警——原理篇</a></li>
<li><a href="/blog/0051-prometheus-in-action-thinking.html">Prometheus监控告警——总结与思考</a></li>
</ul>
<p><a href="/blog/0048-prometheus-in-action-start.html">上一篇</a>我们用Docker单机运行了一套Prometheus监控系统，但不足以应用到生产环境，主流方式是用Prometheus Operator在Kubernetes集群中部署。本篇我们就从Prometheus Operator开始，以三个典型问题为例，讲解在生产环境中使用Prometheus系统的实战经验。</p>
<p>（阅读本篇需要一些Kubernetes的背景知识）</p>
<h3 id="一-声明式管理监控告警系统" tabindex="-1">一：声明式管理监控告警系统 <a class="header-anchor" href="#一-声明式管理监控告警系统" aria-label="Permalink to &quot;一：声明式管理监控告警系统&quot;">&ZeroWidthSpace;</a></h3>
<p><a href="https://github.com/coreos/prometheus-operator" target="_blank" rel="noreferrer">Prometheus Operator</a>是CoreOS的一个开源项目，用来增强Prometheus在Kubernetes中的管理运维能力。利用Kubernetes的<strong>自定义资源定义</strong> （Custom Resource Definition）的特性，实现<strong>声明式</strong>管理运维Prometheus监控告警系统。</p>
<h4 id="部署和配置prometheus-operator" tabindex="-1">部署和配置Prometheus Operator <a class="header-anchor" href="#部署和配置prometheus-operator" aria-label="Permalink to &quot;部署和配置Prometheus Operator&quot;">&ZeroWidthSpace;</a></h4>
<p>常用的部署Prometheus Operator的方式有两种：</p>
<ul>
<li><a href="https://github.com/helm/charts" target="_blank" rel="noreferrer">Helm Chart</a>部署</li>
<li>直接用Kubectl命令Apply配置清单：<a href="https://github.com/prometheus-operator/kube-prometheus" target="_blank" rel="noreferrer">Manifest</a>。</li>
</ul>
<p>Helm中心仓库的PrometheusOperator Chart，在2020年9月已经不维护了，新的Chart在Prometheus Community的Repo中，叫 <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack" target="_blank" rel="noreferrer">kube-prometheus-stack</a>，使用时需要先添加该Repo。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prometheus-community</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://prometheus-community.github.io/helm-charts</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prometheus-community/kube-prometheus-stack</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>该<a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack" target="_blank" rel="noreferrer">Helm Chart</a>与其他Chart类似，把大量参数封装了，要深入使用仍要一个一个参数阅读，定制Values文件的参数来部署一套符合需求的监控告警系统。</p>
<p>因此如果<strong>想定制的参数不在Helm Values中</strong>，仍然需要修改Chart中的模板文件。所以有时候返璞归真，直接用静态Yaml直接Apply到集群也是一个不错的办法。</p>
<p>我个人更喜欢<strong>第二种方式</strong>，克隆<a href="https://github.com/prometheus-operator/kube-prometheus" target="_blank" rel="noreferrer">kube-prometheus项目</a>，在Readme中找到与Kubernetes对应的版本（如下图），切到对应的Git Branch修改定制的参数后，执行命令即可。</p>
<p><img src="//filecdn.code2life.top/operator-version-matrix.png" alt=""></p>
<blockquote>
<p>git clone <a href="https://github.com/prometheus-operator/kube-prometheus.git" target="_blank" rel="noreferrer">https://github.com/prometheus-operator/kube-prometheus.git</a>
git checkout release-0.6  # 视情况而定<br>
kubectl apply -f manifests</p>
</blockquote>
<p>其中要改的参数有：服务入口的Ingress配置、存储的PersistentVolume、定制的监控端点、告警通道等等，阅读Manifests的Yaml代码大概就明白了。</p>
<h4 id="使用-operator-crd-创建监控告警" tabindex="-1">使用 Operator CRD 创建监控告警 <a class="header-anchor" href="#使用-operator-crd-创建监控告警" aria-label="Permalink to &quot;使用 Operator CRD 创建监控告警&quot;">&ZeroWidthSpace;</a></h4>
<p>部署的过程中，除了会自动创建Prometheus Operator、Prometheus Adapter、Kube-state-metrics、Grafana等组件，以及相关的ServiceAccount/Role/RoleBinding/ConfigMap/Secrets之外，还会创建一些Kubernetes CRD：</p>
<ul>
<li><strong>Prometheus</strong>: prometheuses.monitoring.coreos.com</li>
<li><strong>PrometheusRule</strong>: prometheusrules.monitoring.coreos.com</li>
<li><strong>ServiceMonitor</strong>: servicemonitors.monitoring.coreos.com</li>
<li><strong>PodMonitor</strong>: podmonitors.monitoring.coreos.com</li>
<li><strong>AlertManager</strong>: alertmanagers.monitoring.coreos.com</li>
<li><strong>ThanosRuler</strong>: thanosrulers.monitoring.coreos.com</li>
</ul>
<p><strong>Prometheus</strong>和<strong>AlertManager</strong>这两个CRD部署的时候就自行生成了，用来自动创建PrometheusServer和AlertManager的StatefulSet，几乎不需要额外修改，常用的需要修改的CRD是：<strong>ServiceMonitor、PrometheusRules</strong>。</p>
<p><strong>ServiceMonitor</strong>是对Scrape Config的抽象，利用Kubernetes的<strong>selector机制</strong>非常灵活地选取监控端点，比如下面这个Prometheus自监控的ServiceMonitor的配置。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring.coreos.com/v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ServiceMonitor</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">prometheus</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  endpoints</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">30s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">web</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  selector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    matchLabels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      prometheus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">k8s</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>该ServiceMonitor Apply到集群后，Prometheus配置中会生成一个这样的Scrape Config。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">job_name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring/prometheus/0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  scrape_interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">30s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  scrape_timeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">10s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  metrics_path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/metrics</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  scheme</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">http</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  kubernetes_sd_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">role</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">endpoints</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    namespaces</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      names</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  relabel_configs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">以下省略...</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p><strong>PrometheusRule</strong>是对Record Rule/Alert Rule的抽象，在做告警规则时经常使用。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring.coreos.com/v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">PrometheusRule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">basic-rules</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">monitoring</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  groups</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">service-missing-alert</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    rules</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">My-Service-Missing</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      annotations</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">My-Service-Missing</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        summary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">My-Service-Missing</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      expr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">absent(process_uptime_seconds{application="my-service"})</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">15s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        severity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">error</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>这个Yaml文件Apply到集群后，会生成一个AlertRule，本例中“MyService”的uptime指标持续15秒不见的时候(absent)，触发告警。</p>
<p>了解这些CRD的使用后，我们就能声明式管理监控告警系统，以及系统中的监控端点和告警规则等配置了，配置的修改也会自动加载，无需手动重启。</p>
<h3 id="二、细粒度监控指标" tabindex="-1">二、细粒度监控指标 <a class="header-anchor" href="#二、细粒度监控指标" aria-label="Permalink to &quot;二、细粒度监控指标&quot;">&ZeroWidthSpace;</a></h3>
<p>单独部署的Exporter自带的监控指标，通常是不够的，编程语言、框架的Runtime指标、业务服务自定义的监控指标都非常关键。比如JVM中的线程池监控、某业务执行时间监控等等，下面来看看实战中怎么更进一步，集成更多的细粒度监控指标。</p>
<h4 id="场景1-prometheus监控java-springboot应用" tabindex="-1">场景1：Prometheus监控Java SpringBoot应用 <a class="header-anchor" href="#场景1-prometheus监控java-springboot应用" aria-label="Permalink to &quot;场景1：Prometheus监控Java SpringBoot应用&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>Step1：集成SpringBoot Actuator</strong></p>
<p>SpringBoot Actuator是SpringBoot自带的应用监控管理组件，配合<strong>SpringBoot Admin</strong>可以实现一整套对微服务的监控管理，启用Actuator只需要添加一个依赖（Gradle形式）：</p>
<blockquote>
<p>&quot;org.springframework.boot:spring-boot-starter-actuator&quot;</p>
</blockquote>
<p><strong>Step2：再添加Prometheus Registry的依赖</strong></p>
<blockquote>
<p>&quot;io.micrometer:micrometer-registry-prometheus&quot;</p>
</blockquote>
<p>这个jar实现了自定义的Actuator Endpoint（<strong>/actuator/prometheus</strong>），部署后在Prometheus Server上添加静态配置或自动发现规则，即可监控到服务端点，刮取/actuator/prometheus下的Metrics数据。</p>
<p><strong>注</strong>：Actuator里面有很多非常敏感的API，比如/threaddump /heapdump /shutdown，一般在非本地环境都会配置关闭，比如只启用这几个，并且在入口做IP或者URL的访问限制。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># application.yaml</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">management</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  endpoints</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    web</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      exposure</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        include</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"info,health,prometheus"</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p><strong>Step3：JVM指标可视化</strong></p>
<p>应用服务部署后，创建PodMonitor或自定义发现规则，收集到Prometheus中，再到Grafana中导入 JVM Micrometer(4701), Spring Boot Statistics(6756) 这两个Dashboard。</p>
<p>另一个常用的Java应用集成方式是在<strong>JVM启动时指定开启JMX，搭配JMX Exporter收集JVM监控数据</strong>，虽然可靠性更佳，但扩展性不如actuator，建议在传统<strong>非SpringBoot</strong>基础上的Java服务上使用JMX Exporter。</p>
<p><strong>Step4：利用Micrometer添加自定义监控指标</strong></p>
<p>SpringBoot Actuator使用的是<strong>Micrometer</strong>。Micrometer之于PrometheusRegistry就像Slf4J之于Log4j/Logback, 也就是说<strong>Micrometer与Slf4J一样</strong>，提供了一套监控<strong>Facade</strong>，<strong>不关心真正的实现（MetricsRegistry）是什么</strong>。</p>
<p>因此我们调用Micrometer的API即可实现自定义监控，真正的实现类在micrometer-registry-prometheus，即使不用Prometheus，换一个Registry的依赖即可。</p>
<p>Spring官方博客有关于这方面的介绍：<a href="https://spring.io/blog/2018/03/16/micrometer-spring-boot-2-s-new-application-metrics-collector" target="_blank" rel="noreferrer">https://spring.io/blog/2018/03/16/micrometer-spring-boot-2-s-new-application-metrics-collector</a></p>
<p>最简单的用法，可以将Micrometer中的TimedAspect类添加到Spring容器，TimedAspect自带的AOP注解@Timed，添加到被动态代理类的方法上就能实现<strong>方法执行时间监控</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Configuration</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TimedAspectConfiguration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Bean</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> TimedAspect </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">timedAspect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MeterRegistry </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">registry</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TimedAspect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(registry);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 在方法上添加 @Timed 注解即可</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Timed</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "my_method_execution"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">extraTags</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"abc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> myMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // do something</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>启动服务后，访问本地的/actuator/prometheus接口，就能看到如下的指标数据了，其中就有我们自定义的&quot;my_method_execution&quot;的三个指标（count sum max）。</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span># HELP my_method_execution_seconds </span></span>
<span class="line"><span># TYPE my_method_execution_seconds summary</span></span>
<span class="line"><span>my_method_execution_seconds_count{class="a.b.c.MyService",method="myMethod",type="abc"} 2.0</span></span>
<span class="line"><span>my_method_execution_seconds_sum{class="a.b.c.MyService",method="myMethod",type="abc"} 35.0287226</span></span>
<span class="line"><span># HELP my_method_execution_seconds_max </span></span>
<span class="line"><span># TYPE my_method_execution_seconds_max gauge</span></span>
<span class="line"><span>my_method_execution_seconds_max{class="a.b.c.MyService",method="myMethod",type="abc"} 34.4211514</span></span>
<span class="line"><span></span></span>
<span class="line"><span># TYPE jvm_memory_used_bytes gauge</span></span>
<span class="line"><span>jvm_memory_used_bytes{area="nonheap",id="Code Cache",} 4424384.0</span></span>
<span class="line"><span>jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 5.9739904E7</span></span>
<span class="line"><span>jvm_memory_used_bytes{area="heap",id="Eden Space",} 1.0601656E7</span></span>
<span class="line"><span>jvm_memory_used_bytes{area="heap",id="Survivor Space",} 2318720.0</span></span>
<span class="line"><span>jvm_memory_used_bytes{area="heap",id="Tenured Gen",} 8.8107176E7</span></span>
<span class="line"><span># ......</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><p>如果不用自带的@Timed注解，也可以查阅文档调用Micrometer的API，下面是创建一个Gauge的例子。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">createGauge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(someObj, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"my_gauge"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Customized Gauge to monitor something"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, someObj </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">double</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) someObj.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getRealTimeMetricsData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">T</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createGauge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(T weakRef, String metrics, String help, ToDoubleFunction</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">T</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> measure) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    Gauge.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">builder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(metrics, weakRef, measure)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(help)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">register</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.registry);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h3 id="场景2-prometheus监控node-js服务" tabindex="-1">场景2：Prometheus监控Node.js服务 <a class="header-anchor" href="#场景2-prometheus监控node-js服务" aria-label="Permalink to &quot;场景2：Prometheus监控Node.js服务&quot;">&ZeroWidthSpace;</a></h3>
<p>Node.js表示：我们不学Java界搞<strong>花里胡哨的针对接口编程、过度封装</strong>，要接入Prometheus直接2行代码搞定！</p>
<p><strong>Step1：集成prom-client</strong></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// npm i prom-client</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { collectDefaultMetrics } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'prom-client'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">collectDefaultMetrics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({ timeout: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> });</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>这里用到的<a href="https://github.com/siimon/prom-client" target="_blank" rel="noreferrer">prom-client</a>是一个Node.js的Prometheus客户端的开源实现，我读完源码后，一开始担心<strong>对象keys遍历的操作</strong>和<strong>精确的纳秒时间获取</strong>会不会性能不高，对比压测之后发现对普通的HTTP接口的<strong>性能影响在5%以内</strong>，可以放心使用。</p>
<p>再后来想明白，源码用<strong>process.hrtime()而不是Date.now()是有深意的</strong>, 因为<strong>NodeJS中Date.now()/new Date()并不一定是准确时间</strong>，而<strong>process.hrtime()可以获取机器的精确到纳秒</strong>的时间，虽然性能与Date.now()差了一个数量级，但这对监控方法执行时间是<strong>必要</strong>的！</p>
<p><strong>Step2：收集指标数据及可视化</strong></p>
<p>同样，定义ServiceMonitor或配置Scrap Config将时序数据收集到Prometheus。</p>
<p>再到Grafana导入我开发的Node.js Dashboard（11159）: <a href="https://grafana.com/grafana/dashboards/11159" target="_blank" rel="noreferrer">https://grafana.com/grafana/dashboards/11159</a>，即可看到丰富的Node.js运行时监控了，最关键的有：EventLoop的延迟，V8的内存等等。</p>
<p><strong>Step3：自定义指标</strong></p>
<p>以koa2 + TypeScript为例，下面这些代码能给每个HTTP接口自动加上计时监控，访问/metrics接口获取监控数据。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { yourRouter} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './routes'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { collectDefaultMetrics, register, Counter, Gauge } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'prom-client'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Router </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'koa-router'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">startTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'pino-http'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">yourRouter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'/metrics'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ctx.headers[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'content-type'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> register.contentType;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ctx.body </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> register.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">metrics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> httpMetricsLabelNames</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'method'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'path'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> totalHttpRequestCount</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Counter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'nodejs_http_total_count'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  help: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'total request number'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  labelNames: httpMetricsLabelNames</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> totalHttpRequestDuration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Gauge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'nodejs_http_total_duration'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  help: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'the last duration or response time of last request'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  labelNames: httpMetricsLabelNames</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> initMetrics4EachRoute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">layer</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Router</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Layer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  layer.stack.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">unshift</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    totalHttpRequestCount.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ctx.method, layer.path).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">inc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // start time symbol defined in pino-http</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    totalHttpRequestDuration</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ctx.method, layer.path)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">inc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">valueOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (ctx.res </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> any</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)[startTime]);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> initRoutingMetrics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  yourRouter.stack.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">forEach</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(initMetrics4EachRoute);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div><h3 id="场景3-prometheus监控golang服务" tabindex="-1">场景3：Prometheus监控Golang服务 <a class="header-anchor" href="#场景3-prometheus监控golang服务" aria-label="Permalink to &quot;场景3：Prometheus监控Golang服务&quot;">&ZeroWidthSpace;</a></h3>
<p><strong>Step1：集成client_golang库</strong></p>
<p>Prometheus官方提供了Golang的Client Library（<a href="https://github.com/prometheus/client_golang" target="_blank" rel="noreferrer">https://github.com/prometheus/client_golang</a>），毕竟Prometheus本身也是Golang写的，集成方式也非常简单，把client_golang自带的Handler注册到路由中即可。</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">flag</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">net/http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">github.com/prometheus/client_golang/prometheus/promhttp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> addr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> flag.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"listen-address"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":8080"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"The address to listen on for HTTP requests."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	flag.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	http.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Handle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/metrics"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, promhttp.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Handler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	log.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Fatal</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(http.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ListenAndServe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">addr, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>可视化 Grafana可以使用 Golang Go Processes(6671) 这个Dashboard。</p>
<p><img src="//filecdn.code2life.top/go_process.png" alt=""></p>
<p><strong>Step2：Golang应用自定义监控指标</strong></p>
<p>自定义指标也可以参考client_golang的示例代码：<a href="https://github.com/prometheus/client_golang/blob/master/examples/random/main.go%E3%80%82" target="_blank" rel="noreferrer">https://github.com/prometheus/client_golang/blob/master/examples/random/main.go。</a></p>
<p>大致流程如下所示，创建指标、注册到Registry中、再随程序运行记录指标值。</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  someHistogram </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> prometheus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewHistogram</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">prometheus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">HistogramOpts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		Name:    </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"my_histogram_seconds"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		Help:    </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Some distributions."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		Buckets: prometheus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LinearBuckets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">normMean</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">**</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">normDomain, .</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">**</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">normDomain, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	})</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  prometheus.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">MustRegister</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(someHistogram)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  someHistogram.(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">prometheus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ExemplarObserver</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  ObserveWithExemplar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">				v, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">prometheus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"label"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h3 id="三、监控告警系统的高可用" tabindex="-1">三、监控告警系统的高可用 <a class="header-anchor" href="#三、监控告警系统的高可用" aria-label="Permalink to &quot;三、监控告警系统的高可用&quot;">&ZeroWidthSpace;</a></h3>
<p>在实际使用中，除了经常需要定制监控指标外，还需要考虑监控告警系统自身的可用性。我们用Prometheus Operator部署，PrometheusServer和AlertManager默认都是<strong>高可用</strong>的。默认情况下，Prometheus是两个实例，AlertManager是三个实例，多个部署实例在原理上是如何做到高可用的呢？</p>
<h4 id="prometheus系统自身的高可用" tabindex="-1">Prometheus系统自身的高可用 <a class="header-anchor" href="#prometheus系统自身的高可用" aria-label="Permalink to &quot;Prometheus系统自身的高可用&quot;">&ZeroWidthSpace;</a></h4>
<p>两个Prometheus实例是<strong>各自独立抓取所有指标</strong>。也就是说<strong>每个监控端点会被重复抓取</strong>，<strong>冗余存储</strong>在两个Prometheus Server实例绑定的PersistentVolume中，由于单盘容量有限，一般不会保存太久。</p>
<p>这种方式的高可用在单集群已经足够了，但如果不想直接存在硬盘或需要接入多集群数据，可以配置Remote Write，比如写入InfluxDB。以此将<strong>数据存储</strong>的扩展性和高可用问题移到了Prometheus系统外部，用其他方案解决。</p>
<p>仅仅配置一个Remote Write性能会打折扣，如果不是为了兼容其他系统一般不会这么做。Prometheus开源生态体系中有一个<a href="https://thanos.io/" target="_blank" rel="noreferrer">Thanos</a>项目，提供了多集群长期存储的<strong>整套解决方案</strong>，具体细节<a href="https://www.jianshu.com/p/b1f74d552b12" target="_blank" rel="noreferrer">这里</a>有一篇文章讲解。这种高可用方案更加完备，但也为弹性能力引入了额外的复杂度，适用于多集群超大量的监控数据存储。</p>
<p>另外，AlertManager本身使用Gossip协议防止重复告警，具体原理下一篇会讲。因此这样一套<strong>单集群</strong>监控告警系统就自身组件没有单点了，但多数据中心、多集群的场景会更加复杂，以后再展开讨论。</p>
<h4 id="自监控与watch-dog" tabindex="-1">自监控与Watch Dog <a class="header-anchor" href="#自监控与watch-dog" aria-label="Permalink to &quot;自监控与Watch Dog&quot;">&ZeroWidthSpace;</a></h4>
<p>部署多个实例不代表100%可用，极端的例子是整个数据中心都宕机了。那么，<strong>监控系统怎么知道监控系统是正常工作呢</strong>？</p>
<p>一方面，可以从自监控入手，比如内部组件之间互相监控，甚至<strong>多数据中心之间互相监控巡检</strong>；另一方面可以利用<strong>Watch Dog机制</strong>，一般专业的告警通知渠道（比如PagerDuty）都有这类机制，没有的话也可以二次开发实现。</p>
<p>Watch Dog在硬件领域指的是一个独立运行的计时器电路，主程序未能及时清除计时器，就说明主程序可能不正常了，Watch Dog触发重启等指令。</p>
<p>在监控告警系统中的Watch Dog含义也类似，大概流程是这样的：</p>
<ul>
<li>Prometheus会<strong>一直触发</strong>一个特殊的告警，告诉第三方告警通知渠道，“我还能报警，我没有挂”；</li>
<li>接收方过滤掉这条消息不予发送，并重置计时器；</li>
<li>一旦超过一段时间，还没有收到这条空告警消息，就说明AlertManager到消息发送系统之间的链路断了；</li>
<li>“看门狗”叫起来，发出告警消息。</li>
</ul>
<p>要使用Watch Dog机制，创建一个vector(1)的Alert Rule到Prometheus，再配置或开发告警渠道即可。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Watchdog</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  annotations</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    message</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      This is an alert meant to ensure that the entire alerting pipeline is functional.</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      This alert is always firing, therefore it should always be firing in Alertmanager</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      and always fire against a receiver. There are integrations with various notification</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      mechanisms that send a notification when this alert is not firing. For example the</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "DeadMansSnitch" integration in PagerDuty.</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  expr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">vector(1)</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    severity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">none</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h4 id="多渠道告警" tabindex="-1">多渠道告警 <a class="header-anchor" href="#多渠道告警" aria-label="Permalink to &quot;多渠道告警&quot;">&ZeroWidthSpace;</a></h4>
<p>高可用<strong>不只是</strong>内部没有单点，可用性需要考虑到整个业务流程上的<strong>每一个环节</strong>。<strong>如果消息通知渠道同时也挂了怎么办呢？</strong></p>
<p>我们的服务可能会挂，微信也可能会挂，邮件服务也可能会挂，虽然它们同时挂掉的可能性极低。</p>
<p>监控系统的末端——消息通知渠道，也应该不是单点。解决方案一般是在AlertManager配置<strong>多渠道冗余告警</strong>，来保障告警消息的可达性。</p>
<h3 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h3>
<p>本篇主要讲解了生产环境中使用Prometheus监控告警系统最常见的三个问题：</p>
<ul>
<li>在Kubernetes中用Prometheus Operator声明式管理运维监控告警系统</li>
<li>不同编程语言和框架中自定义监控指标，实现细粒度监控</li>
<li>监控告警系统的高可用实现</li>
</ul>
<p>下一篇，我们深入源码寻找Prometheus系统关键流程的代码实现，了解底层技术原理。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Prometheus监控告警——原理篇]]></title>
            <link>https://code2life.top/blog/0050-prometheus-in-action-impl</link>
            <guid>https://code2life.top/blog/0050-prometheus-in-action-impl</guid>
            <pubDate>Wed, 26 Feb 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-prometheus监控告警——原理篇" tabindex="-1">[DevOps] Prometheus监控告警——原理篇 <a class="header-anchor" href="#devops-prometheus监控告警——原理篇" aria-label="Permalink to &quot;[DevOps] Prometheus监控告警——原理篇&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li><a href="/blog/0048-prometheus-in-action-start.html">Prometheus监控告警——基础篇</a></li>
<li><a href="/blog/0049-prometheus-in-action-usage.html">Prometheus监控告警——实战篇</a></li>
<li><a href="/blog/0050-prometheus-in-action-impl.html">Prometheus监控告警——原理篇</a></li>
<li><a href="/blog/0051-prometheus-in-action-thinking.html">Prometheus监控告警——总结与思考</a></li>
</ul>
<p>前两篇我们了解了如何使用Prometheus系统，这一篇我们深入Prometheus源码，对最关键的核心代码一探究竟。</p>
<h2 id="总览" tabindex="-1">总览 <a class="header-anchor" href="#总览" aria-label="Permalink to &quot;总览&quot;">&ZeroWidthSpace;</a></h2>
<p>Prometheus系统在Github下有许多项目，我们只关注最关键的部分，也就是<a href="https://github.com/prometheus/prometheus.git" target="_blank" rel="noreferrer">Prometheus Server</a>的实现。下图框出来的几个子目录是最核心的几个模块。</p>
<p><img src="//filecdn.code2life.top/prom-src.png" alt=""></p>
<p>我们逐步分析其中最最核心的几点：</p>
<p>阅读源码最好的办法就是一行一行跟断点，Prometheus代码结构一目了然，命名简洁清晰，我们找到入口文件：cmd/prometheus/main.go，在Debugger模式下编译启动就可以运行了。</p>
<p>Prometheus并没有使用依赖注入框架，入口的main函数比较庞大，包括大量的初始化操作，在第500行之后才是真正启动的地方。</p>
<p>项目用了 <a href="https://github.com/oklog/run" target="_blank" rel="noreferrer">oklog/run</a> 这个goroutine编排工具，并行启动关键的几个子模块。</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> g </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Group</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Termination handler </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 接受SIGTERM信号优雅退出</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Scrape discovery manager</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 自动发现监控端点的goroutine</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  discoveryManagerScrape.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Notify discovery manager</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 自动发现AlertManager端点的goroutine</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  discoveryManagerNotify.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Scrape manager</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 刮取监控数据的关键入口</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 传入channel是因为当对应的 discovery 结果变化时需要reload</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  scrapeManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(discoveryManagerScrape.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SyncCh</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 添加一些其他的goroutine：</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Reload handler / Initial configuration loading / ...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// TSDB：启动时序数据库的关键入口</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  ...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  db, err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> openDBWithMetrics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Web handler：启动 HTTP/gRPC 服务的入口</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  ...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  webHandler.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ctxWeb)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Notifier：发送告警通知给AlertManager的入口</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  notifierManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(discoveryManagerNotify.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SyncCh</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br></div></div><h2 id="tsdb是如何实现的" tabindex="-1">TSDB是如何实现的 <a class="header-anchor" href="#tsdb是如何实现的" aria-label="Permalink to &quot;TSDB是如何实现的&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li>时序向量</li>
<li>时序数据压缩</li>
<li>倒排索引</li>
<li>源码</li>
</ul>
<h2 id="告警消息如何产生" tabindex="-1">告警消息如何产生 <a class="header-anchor" href="#告警消息如何产生" aria-label="Permalink to &quot;告警消息如何产生&quot;">&ZeroWidthSpace;</a></h2>
<p>求值 - 阈值判断 - 触发 - 分组 - 等待 - 处理链路 - 路由链路 - 发送 - 结束</p>
<p>Gossip协议，去重实现</p>
<p>AlertManager HTTP API
<a href="https://zhuanlan.zhihu.com/p/42190073" target="_blank" rel="noreferrer">https://zhuanlan.zhihu.com/p/42190073</a></p>
<p>告警收敛，从源头上避免疲劳告警。</p>
<blockquote>
<p>警报疲劳（Alarm Fatigue）是指暴露在大量、频繁的警报之中，被暴露者产生的去敏感化现象。去敏感化现象会导致更长的反应时间，甚至是忽视重要的警报。</p>
</blockquote>
<p>Prometheus的杀手锏是自发现的Pull模型代替传统的Push模型，而AlertManager的杀手锏是强大的告警收敛机制。什么是告警收敛？就是通过一系列预处理手段，尽可能的归类、合并、减少最终发送给用户的警报，让用户收到最简单又最重要的信息。AlertManager做了一个大胆的尝试，不允许配置最大告警次数，这在传统的主流告警系统中是史无前例的，AlertManager这样设计的目的就是期望在强大的收敛机制下，告警信息能够万元归一。</p>
<p><a href="https://github.com/prometheus/alertmanager/blob/master/doc/arch.svg" target="_blank" rel="noreferrer">https://github.com/prometheus/alertmanager/blob/master/doc/arch.svg</a></p>
<h2 id="思考" tabindex="-1">思考 <a class="header-anchor" href="#思考" aria-label="Permalink to &quot;思考&quot;">&ZeroWidthSpace;</a></h2>
<p>时序数据库的本质</p>
<p>LSM Tree</p>
<p>告警收敛</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Prometheus监控告警——总结与思考]]></title>
            <link>https://code2life.top/blog/0051-prometheus-in-action-thinking</link>
            <guid>https://code2life.top/blog/0051-prometheus-in-action-thinking</guid>
            <pubDate>Tue, 25 Feb 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-prometheus监控告警——总结与思考" tabindex="-1">[DevOps] Prometheus监控告警——总结与思考 <a class="header-anchor" href="#devops-prometheus监控告警——总结与思考" aria-label="Permalink to &quot;[DevOps] Prometheus监控告警——总结与思考&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="目录" tabindex="-1">目录 <a class="header-anchor" href="#目录" aria-label="Permalink to &quot;目录&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li><a href="/blog/0048-prometheus-in-action-start.html">Prometheus监控告警——基础篇</a></li>
<li><a href="/blog/0049-prometheus-in-action-usage.html">Prometheus监控告警——实战篇</a></li>
<li><a href="/blog/0050-prometheus-in-action-impl.html">Prometheus监控告警——原理篇</a></li>
<li><a href="/blog/0051-prometheus-in-action-thinking.html">Prometheus监控告警——总结与思考</a></li>
</ul>
<p>前面三篇从基本概念到具体实践，再到原理分析，我们大致了解了Prometheus监控告警系统以及周边技术。</p>
<p>本篇主要探讨在实践中遇到的3个<strong>发散性问题</strong>：</p>
<ul>
<li>ELK也可以做监控，和Prometheus有什么区别？</li>
<li>Prometheus在多个数据中心怎么部署？</li>
<li>监控告警系统有哪些最佳实践？</li>
</ul>
<h2 id="prometheus与elk方案对比" tabindex="-1">Prometheus与ELK方案对比 <a class="header-anchor" href="#prometheus与elk方案对比" aria-label="Permalink to &quot;Prometheus与ELK方案对比&quot;">&ZeroWidthSpace;</a></h2>
<p>ELK这个名字不太准确，分布式日志系统通常是基于ElasticSeach，结合Filebeat/Fluentd/Logstash/Kibana等一系列组件一起服用。虽然成本不低，效果也非常明显，除了日志分析、日志流式计算、链路追踪，也可以做<strong>监控告警</strong>。</p>
<p>ElasticSearch的存储和查询能力加上Kibana不输Grafana的可视化能力，做监控毫无压力，Elastic官方也提供了更专业的全家桶方案：Metricbeat + ElasticSearch + Elastic APM + Kibana。这里主要讨论<strong>日志打点</strong>的监控方案和<strong>Prometheus</strong>方案的区别。</p>
<p>ELK栈监控方案有哪些优点呢？</p>
<ul>
<li>对现有系统<strong>侵入小</strong>，比如监控QPS，只需要加一个日志关键字的搜索结果的折线图即可；</li>
<li>可以做<strong>更具体也更灵活</strong>的事情来扩展监控，比如可以基于日志记录Tracing信息，做分布式链路追踪，或是聚合分析Json日志的自定义属性；</li>
<li>可以结合<strong>流式计算</strong>等大数据处理相关技术，配合机器学习做更高阶的事情比如AIOps，而不仅仅是单纯的监控告警。</li>
</ul>
<p>凡是有两面，其局限性也很明显：</p>
<ul>
<li>文本数据往往需要额外的数据清洗转换，做全文索引存下来，才可以做复杂的业务监控，ELK栈各组件也都不是省油的灯，<strong>运维成本较高</strong>；</li>
<li>没有像Prometheus这样生态完善的Exporters，业务监控绰绰有余，但基础设施监控不够方便；</li>
<li>日志的异步处理，使得ELK方案的<strong>实时性</strong>相比时序数据库方案更不稳定；</li>
<li>Elastic的告警功能得花钱买商业版License，<strong>OSS版本或Basic License没有告警功能</strong>，自己开发有一定的成本。</li>
</ul>
<p>其实ELK与Prometheus二者并不矛盾，往往是<strong>结合两种方式，在不同层次上用更合适的方案，实现功能互补</strong>，自底向上我们是这样使用的：</p>
<ul>
<li><strong>物理机/虚拟机节点硬件指标</strong> - Prometheus / 云服务自带的方案（CloudWatch等）</li>
<li><strong>容器以及容器编排系统</strong> - Prometheus 天生完美支持</li>
<li><strong>依赖组件或中间件（数据库、缓存、消息队列等）</strong> - Prometheus / 云服务自带的方案</li>
<li><strong>应用服务运行时(Runtime)</strong> - 集成Prometheus的Instrument库，或挂上对应的Exporter比如JMX Exporter</li>
<li><strong>能够输出日志的具体业务</strong>  - Prometheus &amp; ELK 并用</li>
<li><strong>更加宏观的应用性能管理（APM）时序数据</strong> - Prometheus &amp; ELK 并用</li>
</ul>
<p>也就是说，越偏向业务层，使用ElasticSeach的日志方案做监控优势越明显。</p>
<h2 id="多数据中心的监控告警方案" tabindex="-1">多数据中心的监控告警方案 <a class="header-anchor" href="#多数据中心的监控告警方案" aria-label="Permalink to &quot;多数据中心的监控告警方案&quot;">&ZeroWidthSpace;</a></h2>
<p>我们遇到的另一个挑战是数据中心分布在全球各地。因此需要有集中化的监控告警，实现<strong>多级、立体</strong>的监控告警。在<strong>大于两个数据中心的生产环境</strong>中，Prometheus用起来就没有那么方便了：</p>
<ul>
<li>每个DC部署一套：数据分散，没有<strong>整体监控入口</strong></li>
<li>只部署一套收集所有DC的监控数据：可用性相对较低，容易遇到Prometheus HTTP方式收集监控数据导致的<strong>性能瓶颈</strong></li>
<li>Federation模式：N+1部署加上数据同步，<strong>相对复杂</strong>而且数据复制到顶层Prometheus的可靠性有待考证（<a href="https://www.robustperception.io/federation-what-is-it-good-for" target="_blank" rel="noreferrer">https://www.robustperception.io/federation-what-is-it-good-for</a>）</li>
<li>使用开源项目<a href="https://github.com/thanos-io/thanos" target="_blank" rel="noreferrer">Thanos</a>：通过Sidecar延伸Prometheus的能力，使用云服务商或自建的对象存储作为中心化存储方案。Thanos虽然是很优秀的项目，但涉及的组件较多，部署运维也不简单，另外性能受对象存储的影响比较大。</li>
<li>其他开源项目，如<a href="https://github.com/VictoriaMetrics/VictoriaMetrics" target="_blank" rel="noreferrer">VictoriaMetrics</a>: VictoriaMetrics和Thanos一样，也是CNCF孵化项目，二者有个对比的文章在这里：<a href="https://medium.com/faun/comparing-thanos-to-victoriametrics-cluster-b193bea1683" target="_blank" rel="noreferrer">https://medium.com/faun/comparing-thanos-to-victoriametrics-cluster-b193bea1683</a>。VictoriaMetrics的架构非常简单，直接用Prometheus的Remote Write API写入，但部署维护起来组件也不少。</li>
</ul>
<p>虽然理论上Federation、Thanos、VictoriaMetrics都各有优势，似乎都是不错的选择，但在实践中我们却用了另外一种更加<strong>接地气</strong>的方案，这里分享一下。</p>
<ul>
<li>对于监控：<strong>每个DC一套2实例Prometheus</strong>，不部署顶层Prometheus，但把<strong>Grafana部署在全局，配置N个Prometheus数据源，创建跨DC监控图表选择Mixed数据源即可</strong>；</li>
<li>对于告警：<strong>每个DC部署3实例AlertManager，告警规则在统一的Git仓库维护</strong>；</li>
<li>由于监控系统化整为零，部署分散开，每个DC的Prometheus可以额外监控其他DC的一些服务，互相巡检。</li>
</ul>
<p>这套监控方案实践起来效果不错，一方面每个DC独立的Prometheus确保的收集性能，<strong>全局Grafana虽然查询稍慢</strong>但能够统一入口，只是不方便聚合出所有集群的全局视图；另一方面DC内部的AlertManager直接告警，使得告警链路最短，搭配<strong>冗余告警策略</strong>解决告警系统的可用性和自告警问题。</p>
<p>不过也有个缺点：如果业务体量再大一个数量级、或者需要保留更久的历史数据，这套方案也不可行了，因为没有全局的<strong>降频采样</strong>和集中化存储。</p>
<p>期间走了一个弯路：在Grafana上配置告警。Grafana做图表可视化一流，但目前不太适合做告警：</p>
<ul>
<li>Grafana的Alert并不支持分布式特性，<strong>因为每个实例都会计算所有告警规则</strong>（<a href="https://grafana.com/docs/tutorials/ha_setup/%E2%80%94" target="_blank" rel="noreferrer">https://grafana.com/docs/tutorials/ha_setup/—</a>），多实例部署Alert会有问题。</li>
<li>违背了尽量<strong>减少链路上可能的故障点</strong>这一原则，多一层Grafana挂了导致收不到告警的风险。</li>
<li>Grafana缺少对告警的分组，静默，抑制等功能特性，不满足需求。</li>
</ul>
<p>因此告警方面我们最终使用了<strong>集中</strong>在Git维护告警规则，<strong>分散</strong>部署AlertManager的方案。</p>
<h2 id="监控告警策略最佳实践" tabindex="-1">监控告警策略最佳实践 <a class="header-anchor" href="#监控告警策略最佳实践" aria-label="Permalink to &quot;监控告警策略最佳实践&quot;">&ZeroWidthSpace;</a></h2>
<p>在学习使用过程中，也沉淀了一些&quot;最佳实践&quot;，这里分享出来。</p>
<h4 id="应该关注哪些核心指标" tabindex="-1">应该关注哪些核心指标 <a class="header-anchor" href="#应该关注哪些核心指标" aria-label="Permalink to &quot;应该关注哪些核心指标&quot;">&ZeroWidthSpace;</a></h4>
<p>哪些指标应该告警呢？这个问题可以转换成&quot;<strong>哪些指标会影响到用户</strong>？&quot;，最终我们可以提炼出来四个核心指标&quot;<strong>延迟，流量，错误，饱和度</strong>&quot;。</p>
<ul>
<li><strong>延迟和错误</strong>直接影响了用户，添加告警时尤其要注意延迟中的<strong>长尾效应</strong>；</li>
<li><strong>流量</strong>指标比如PV、UV、QPS、TPS其重要性不言而喻，是商业和技术决策的核心指标，并且与饱和度相关联；</li>
<li><strong>饱和度</strong>可以理解为<strong>当前实际负载占最大承载能力的比例</strong>，一旦超出某个界限可能导致<strong>雪崩效应</strong>。根据历史指标数据<strong>预测承载能力的临界点，能够防患于未然</strong>，比如预警磁盘什么时候将要写满，预测几个月后的用户量是否会让CPU处于高负载等等。</li>
</ul>
<p>结合上节提到的告警收敛，我们一定要<strong>避免&quot;狼来了&quot;问题，减少虚警</strong>。告警过多 = 0 告警。</p>
<h4 id="预留容量-未雨绸缪" tabindex="-1">预留容量，未雨绸缪 <a class="header-anchor" href="#预留容量-未雨绸缪" aria-label="Permalink to &quot;预留容量，未雨绸缪&quot;">&ZeroWidthSpace;</a></h4>
<p>告警规则设置时留出足够的缓冲容量，避免<strong>过饱和</strong>。比如部署两个实例时每个实例峰值CPU不应该超过50%，超过50%就应该告警，否则一旦其中一个在高峰时段挂了，另一个实例是无法承受转移过来的压力的。</p>
<h4 id="分级响应-设置轮值机制" tabindex="-1">分级响应，设置轮值机制 <a class="header-anchor" href="#分级响应-设置轮值机制" aria-label="Permalink to &quot;分级响应，设置轮值机制&quot;">&ZeroWidthSpace;</a></h4>
<p>小问题不应该惊动所有人，大问题应当尽早通知到所有相关人员，需要从流程制度上对各种级别的问题做好预案。<strong>明确分工，确保无障碍沟通机制</strong>，这与抗疫的道理是一样的。</p>
<h4 id="先解决问题-后排查问题根源" tabindex="-1">先解决问题，后排查问题根源 <a class="header-anchor" href="#先解决问题-后排查问题根源" aria-label="Permalink to &quot;先解决问题，后排查问题根源&quot;">&ZeroWidthSpace;</a></h4>
<p>遇到影响用户的问题首先是解决问题，不管是重启，降级还是主备切换，<strong>应该当机立断采取措施</strong>，而不是保留问题现场找原因。</p>
<h4 id="交叉监控-增强监控的纵深和层级" tabindex="-1">交叉监控，增强监控的纵深和层级 <a class="header-anchor" href="#交叉监控-增强监控的纵深和层级" aria-label="Permalink to &quot;交叉监控，增强监控的纵深和层级&quot;">&ZeroWidthSpace;</a></h4>
<p>告警越少越好，上层的监控也越少越好，但底层监控却多多益善。黑盒、白盒监控搭配使用，上层黑盒，底层白盒。</p>
<p>监控指标之间的相关性也是定位根源的利器，举个例子：</p>
<p>传统运维大多<strong>只监控</strong>机器的CPU、内存、网络、磁盘四大件，定位问题的手段就会<strong>有限且低效</strong>。</p>
<p>如果这时发生了一个缓存击穿问题，多一个<strong>业务层的Cache监控</strong>立刻就可以发现问题根因，而不用等到数据库CPU爆掉才发现。</p>
<p>如果再多一层<strong>外部巡检</strong>，立刻就可以推算出事故在哪些区域产生了多大的影响，帮助进一步决策。</p>
<h4 id="监控图表立体化" tabindex="-1">监控图表立体化 <a class="header-anchor" href="#监控图表立体化" aria-label="Permalink to &quot;监控图表立体化&quot;">&ZeroWidthSpace;</a></h4>
<p>例如Prometheus Operator的Helm Chart自带的Kubernetes Dashboard做的就非常棒，从顶层整个Cluster到底层Pod的关键指标，能够很容易<strong>下钻查询</strong>到问题所在。</p>
<p>Dashboard应该是<strong>N维数据立方</strong>，而不是一堆<strong>孤零零的二维图表</strong>。监控图表进行<strong>聚合、分组、立体化</strong>，也就是根据关联性对图表建立有机的层级关系、组合关系，这对<strong>快速定位问题根源</strong>是非常关键的。</p>
<h4 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h4>
<p>其实这里有很多道理是学习了《SRE:Google运维解密》之后，在实战中踩了坑才彻底理解的。《SRE》是一本好书，浓缩了Google保障服务可靠性实践经验的精华，之前写过一篇读后感： <a href="/blog/0041-goole-sre-thinking.html">/0041-goole-sre-thinking</a>。</p>
<p>总之，对于我们做业务开发的程序猿来说，很重要的一点是<strong>不要划清和运维的界限，你的代码90%以上的时间是在运维阶段而非开发阶段，意识到到做好运维远远不是部署到服务器那么简单，开发-运维闭环互相反馈不断成长，才能构建出抗得住狂风暴雨的系统</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Linux内核参数优化及原理]]></title>
            <link>https://code2life.top/blog/0036-linux-kernel-param</link>
            <guid>https://code2life.top/blog/0036-linux-kernel-param</guid>
            <pubDate>Wed, 22 Jan 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-linux内核参数优化及原理" tabindex="-1">[DevOps] Linux内核参数优化及原理 <a class="header-anchor" href="#devops-linux内核参数优化及原理" aria-label="Permalink to &quot;[DevOps] Linux内核参数优化及原理&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>后端业务体量达到一定程度，总会有机会遇到性能瓶颈，这些瓶颈除了出现在代码上，或在依赖组件上，也有可能出在操作系统层面，比如不合适的Linux内核参数。本篇整理一下<strong>常用的内核参数的原理和使用</strong>,尤其是<strong>网络相关参数，以及在容器环境中</strong>需要注意的地方。</p>
<p>Mark一下Linux性能优化大师Brendan Gregg的网站，和Netflix分享的文章，我们遇到问题时，<strong>首先需要用这些利器定位分析产生性能瓶颈的原因</strong>，如果的确是操作系统的瓶颈，<strong>修改Linux内核参数则是不得已而用之的“处方药”</strong>:</p>
<ul>
<li><a href="//www.brendangregg.com/linuxperf.html" target="_blank" rel="noreferrer">//www.brendangregg.com/linuxperf.html</a></li>
<li><a href="https://medium.com/netflix-techblog/linux-performance-analysis-in-60-000-milliseconds-accc10403c55" target="_blank" rel="noreferrer">https://medium.com/netflix-techblog/linux-performance-analysis-in-60-000-milliseconds-accc10403c55</a></li>
</ul>
<p>修改内核参数的方法很简单，root权限下更改 <strong>/etc/sysctl.conf</strong> 文件， 再执行<strong>sysctl -p</strong>即可，或是用<strong>ansible</strong>的sysctl module直接批量修改（不建议用ansible直接批量执行shell命令方式修改内核参数，ansible的sysctl已经保障了幂等性）。下面主要探讨一些原理，以及真实案例，分三个部分：</p>
<ul>
<li>网络相关的内核参数，net.* 中常用的</li>
<li>非网络相关的，如fs.* kernel.* 等等</li>
<li>容器环境中如何使用，从Docker到Kubernetes集群</li>
</ul>
<h2 id="网络相关内核参数" tabindex="-1">网络相关内核参数 <a class="header-anchor" href="#网络相关内核参数" aria-label="Permalink to &quot;网络相关内核参数&quot;">&ZeroWidthSpace;</a></h2>
<p>对于Linux服务器来说，net.* 下面的内核参数是比较常用的，尤其提供TCP的服务来说。先上一个TCP状态转换图帮助理解：</p>
<p><img src="//filecdn.code2life.top/tcp-state.svg" alt=""></p>
<h4 id="net-ipv4-tcp-tw-reuse" tabindex="-1">net.ipv4.tcp_tw_reuse <a class="header-anchor" href="#net-ipv4-tcp-tw-reuse" aria-label="Permalink to &quot;net.ipv4.tcp_tw_reuse&quot;">&ZeroWidthSpace;</a></h4>
<p>tcp_tw_reuse默认为0关闭，设置为1打开，作用是让<strong>处于TIME_WAIT的状态的TCP连接</strong>的资源可以不用等2MSL，1s之后直接被复用，重新发起SYN包，经过<strong>SYN - FIN_ACK- RST - SYN - SYN_ACK</strong>重新进入<strong>ESTABLISHED状态</strong>。</p>
<p>通俗一点解释，比如下面这个图是本机ss命令随便截的，有7个TIME_WAIT状态的TCP连接，可以想象一下，就在2分钟内（Linux默认的2MSL时间），可能有浏览器关闭了页面，或是短连接获取完数据自己关闭，<strong>用ACK消息回复对端FIN之后，仍然不敢直接复用而是进入TIME_WAIT状态</strong>，因为：</p>
<ul>
<li>虽然TCP保证了顺序，但复杂的网络状况可能导致多次包重传，<strong>对端在FIN之前</strong>的 <strong>数据包</strong> 可能都还没有过来，直接复用原来的连接可能会导致新的连接收到上个连接中重传的“幽灵数据包”</li>
<li>担心<strong>收到FIN之后回复的ACK</strong>对端收不到，于是本端苦苦等待网络包最长存在时间的两倍来<strong>兜底</strong></li>
</ul>
<p><img src="//filecdn.code2life.top/socket-statistics.png" alt=""></p>
<p>因此，对于服务器来说，TCP连接在<strong>被动关闭的情况下，并不存在TIME_WAIT状态</strong>，很多时候是<strong>不需要修改这个参数的</strong>，如果出现<strong>TIME_WAIT过多，也不要盲目配置此参数</strong>，想一想原因，netstat找一找是哪些连接在TIME_WAIT，可能的典型场景有这些：</p>
<ul>
<li><strong>用于压测的客户端机器</strong>：作为客户端不停地发起大量TCP连接</li>
<li><strong>短连接调用的微服务场景</strong>：A服务调B服务，B服务调C服务，虽然大家都是服务器，但是互相调用时，<strong>调用方也是客户端</strong>(用http2，gRPC等<strong>只产生少量长连接的RPC协议</strong>除外)</li>
<li><strong>转发大量请求到外部服务</strong>：比如Nginx，HAProxy，Traefik等反向代理，或是服务端有对外调用第三方开放平台服务的场景，因为大部分平台提供的都是HTTP API，比较容易产生TIME_WAIT的积压</li>
</ul>
<p>另外，服务端在<strong>内存充裕</strong>的情况下，也可以增大<strong>net.ipv4.tcp_max_tw_buckets</strong>来提高最大允许的TIME_WAIT状态的TCP连接数量</p>
<p>注：<strong>绝大多数net.ipv4</strong>下的内核参数，在<strong>net.ipv6</strong>下也有同样的参数，如直接提供ipv6服务，配置相同</p>
<h4 id="net-ipv4-tcp-tw-recycle" tabindex="-1">net.ipv4.tcp_tw_recycle <a class="header-anchor" href="#net-ipv4-tcp-tw-recycle" aria-label="Permalink to &quot;net.ipv4.tcp_tw_recycle&quot;">&ZeroWidthSpace;</a></h4>
<p>首先写结论，这个参数<strong>永远都不要用，并且Linux 4.10之后已经移除了这个参数</strong>。这里有一篇写的很好的Blog说明，<a href="https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux" target="_blank" rel="noreferrer">https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux</a>, 本文也参考翻译了这里的部分内容，对于解决TIME_WAIT积压的问题，首先找到是否是应用本身的问题，减少不该建立的连接，基本上tcp_tw_reuse已经足够，recycle是比较危险的，尤其是经过NAT之后的时间戳问题会导致次生灾害。</p>
<h4 id="net-ipv4-tcp-fin-timeout" tabindex="-1">net.ipv4.tcp_fin_timeout <a class="header-anchor" href="#net-ipv4-tcp-fin-timeout" aria-label="Permalink to &quot;net.ipv4.tcp_fin_timeout&quot;">&ZeroWidthSpace;</a></h4>
<p>默认60s，在网络状况很好的情况下可以减少到10-30s,有部分文章解释这个参数是修改2MSL时间来减少TIME_WAIT，这是<strong>错误的解释</strong>。</p>
<p>因为，这个参数只能改<strong>4次挥手第2步完成（收到FIN_ACK）进入FIN_WAIT_2后，最长等待的超时时间</strong>。一般Nginx等反向代理机器配置这个参数，是因为Nginx作为客户端来说，后端服务都是在局域网，理论上网络状况很好，没有必要在FIN_WAIT_2状态等待太久。</p>
<h3 id="net-ipv4-tcp-syncookies" tabindex="-1">net.ipv4.tcp_syncookies <a class="header-anchor" href="#net-ipv4-tcp-syncookies" aria-label="Permalink to &quot;net.ipv4.tcp_syncookies&quot;">&ZeroWidthSpace;</a></h3>
<p>笔者找了几个CentOS和Ubuntu的Server查看，此参数默认已经是打开了，不确定是那个版本开始的，打开可以<strong>防止大部分SYN洪水攻击</strong>。这里有一篇讲解TCP SYN Cookies和SYN Flood攻击的文章：<a href="https://segmentfault.com/a/1190000019292140" target="_blank" rel="noreferrer">https://segmentfault.com/a/1190000019292140</a>。总结一下重点：</p>
<ul>
<li>SYN Flood原理是<strong>伪造大量三次握手的第一次SYN包</strong>，让对端产生大量<strong>半连接状态的TCP连接直至资源耗尽</strong></li>
<li>SYN Cookies防止SYN Flood的原理是<strong>通过记录第一个SYN包部分信息Hash，然后在握手最后一步ACK来校验</strong>，校验成功后才真正分配连接资源</li>
<li>SYN Cookies<strong>消耗少量计算资源</strong>，避免了伪造SYN包导致大量半连接状态的TCP连接</li>
</ul>
<p>SYN Cookies是一种用HMAC手段来达到<strong>延迟初始化和资源分配</strong>的目的，搭配下面两个参数可以对半连接状态做更多的优化：</p>
<ul>
<li><strong>net.ipv4.tcp_synack_retries</strong>： 默认5，如果SYN没有SYN_ACK，默认重试5次，可以适当降低</li>
<li><strong>net.ipv4.tcp_max_syn_backlog</strong>：在达到ESTABLISHED之前，半连接状态的TCP连接最大数量，默认值不同发行版不同，找了几种版本看默认值都在128~512之间，视网络状况和具体应用可以适当调整</li>
</ul>
<p>注：这里的<strong>syn_backlog</strong>和linux中listen系统调用中的<strong>backlog</strong>参数区别在于，listen参数中的backlog是监听的port最大允许的<strong>未ACCEPT的ESTABLISHED状态连接数和SYNC_RCVD状态连接数之和</strong>，而syn_backlog是系统层面最大允许的半连接数(<strong>SYNC_RCVD状态</strong>的连接)之和：</p>
<div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> &#x3C;sys/socket.h></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> listen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> sockfd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> backlog</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* "man listen"命令可以查看参数含义 */</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>linux遵循的<strong>POSIX标准</strong>，并<strong>不完全是TCP标准</strong>，backlog不会影响accept()之后的连接数，而是像一个待处理缓冲区，具体分析可以参考这些文章：</p>
<ul>
<li><a href="//veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html" target="_blank" rel="noreferrer">//veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html</a></li>
<li><a href="https://segmentfault.com/a/1190000019252960" target="_blank" rel="noreferrer">https://segmentfault.com/a/1190000019252960</a></li>
</ul>
<h4 id="net-core-tcp-somaxconn" tabindex="-1">net.core.tcp_somaxconn <a class="header-anchor" href="#net-core-tcp-somaxconn" aria-label="Permalink to &quot;net.core.tcp_somaxconn&quot;">&ZeroWidthSpace;</a></h4>
<p>这个参数也有不少人<strong>误解</strong>，有些Nginx的Tuning方案认为这是<strong>最大连接数</strong>，建议把这个值从默认值128改大一些，甚至改到65535，这种理解是<strong>不正确</strong>的。</p>
<p>首先来理解一下这个参数的含义，somaxconn<strong>不是</strong>指每个listen端口的的最大连接数，而是指 <strong>max backlogged connections</strong>, backlog的含义可以看上面的文章，大致可以理解为在<strong>应用层accept之前</strong>的缓冲区大小。</p>
<p>因此，如果<strong>服务端处理能力有盈余，及时accept了，就没必要调整这个参数</strong>了，尤其是现在主流框架都是<strong>单独的I/O线程循环accept和read</strong>，真正的处理都放到Worker线程，128足矣，边缘入口服务如<strong>Nginx机器改成512（Nginx默认listen backlog参数为511）也足矣</strong>。</p>
<p>这个参数可以<strong>在服务端繁忙时缓解connection refuse</strong>的说法的没有错的。比如：</p>
<ul>
<li>秒杀，抢票这种对边缘流量入口节点产生瞬间大流量冲击的场景</li>
<li>预防或缓解DDoS攻击</li>
</ul>
<p>这些情况下是可以增大somaxconn的值作为<strong>辅助手段</strong>的。毕竟，<strong>让用户等待转圈圈总比浏览器直接跳出来CONNECTION_REFUSED要好</strong>。但是话又说回来，又有多少互联网企业会遇到上述两种极高并发的场景呢？</p>
<h4 id="net-ipv4-ip-local-port-range" tabindex="-1">net.ipv4.ip_local_port_range <a class="header-anchor" href="#net-ipv4-ip-local-port-range" aria-label="Permalink to &quot;net.ipv4.ip_local_port_range&quot;">&ZeroWidthSpace;</a></h4>
<p>默认值&quot;32768 60999&quot;,含义是端口号从32768到60999都可以作为建立TCP连接的端口，默认接近3w个连接基本足够了，使用场景与tcp_tw_reuse类似。优先去找<strong>过多连接导致端口号耗尽的根本原因</strong>，切忌盲目修改内核参数，即使看起来没有太大副作用。</p>
<h4 id="net-ipv4-tcp-congestion-control" tabindex="-1">net.ipv4.tcp_congestion_control <a class="header-anchor" href="#net-ipv4-tcp-congestion-control" aria-label="Permalink to &quot;net.ipv4.tcp_congestion_control&quot;">&ZeroWidthSpace;</a></h4>
<p>能够修改<strong>TCP拥塞控制算法</strong>，低内核版本的Linux就不用改这个了，在<strong>4.9及以上版本的内核，Linux提供了新的TCP拥塞控制算法BBR</strong>。说到拥塞控制，又是一个很大的话题了，大学期间学习的《计算机网络》告诉我们，滑动窗口像是一个TCP传输数据的发送-确认缓冲区，而<strong>拥塞窗口cwnd和接收窗口rwnd</strong>共同控制着传输的滑动窗口的大小，滑动窗口的大小直接决定了传输的吞吐量。</p>
<p>因此，一个合适的<strong>拥塞控制算法</strong>会影响服务传输数据的<strong>RTT和带宽利用率</strong>，这对高I/O的应用，如<strong>文件下载，多媒体播放，音视频通信等</strong>服务端是非常重要的，毕竟这些应用动辄传输GB级别的数据，而不是一般Web应用传输KB, MB级别的页面或JSON数据。</p>
<p>传统的拥塞控制算法：</p>
<ul>
<li><strong>reno</strong>：在不同阶段采用<strong>慢启动，拥塞避免，快速恢复，快速重传</strong>这些不同策略，比如一开始<strong>指数增长</strong>窗口大小，到达<strong>慢开始门限（ssthresh）<strong>后进入</strong>线性增长</strong>的拥塞避免阶段，遇到丢包，RTT延长等事件再降下去</li>
<li><strong>cubic</strong>：reno进化版，<strong>目前大部分Linux版本默认的拥塞控制算法</strong>，由三次函数控制，宏观上可以分为<strong>稳定阶段，探测阶段，公平收敛阶段</strong>，能比较平滑地<strong>试探最大带宽</strong></li>
</ul>
<p>新的拥塞控制算法 BBR：</p>
<p>这个算法的具体原理和实现笔者没有继续深究，看了一些资料，思路大体上是站在更高维度去探测低延迟高吞吐的点，<strong>不拘泥于个别包的RTT增加或丢包这种偶然事件</strong>，而传统的拥塞控制算法的思路感觉是“在出现意外事件赶紧降窗”。</p>
<p>举个栗子，传统的拥塞控制算法，下载文件时可能看到的是这样的： 10Kb/s,20Kb/s,...5Mb/s,5.1Mb/s..., 这时出现了丢包事件,瞬间2.5Mb/s, 3Mb/s，而BBR可能看到是一直是6Mb/s, 因为BBR更关注全局情况，更不容易受偶然事件影响，与最大带宽的契合度更高。</p>
<p>虽然没完全搞清楚原理，但笔者最直观的感受是，海外购置的科学上网VPS服务器<strong>开启BBR后，下载文件的速度大约是原来的2倍，网速忽上忽下的感觉少了很多</strong>。Mark一下这两个链接，一个是各种拥塞控制算法的详细讲解，另一个是IETF上公开的算法详解：</p>
<ul>
<li><a href="https://cloud.tencent.com/developer/article/1369617" target="_blank" rel="noreferrer">https://cloud.tencent.com/developer/article/1369617</a></li>
<li><a href="https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00" target="_blank" rel="noreferrer">https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00</a></li>
</ul>
<p>如今随着网络基础设施的发展，千兆万兆带宽遍地都是，<strong>BBR更适合</strong>相比于十年前更加<strong>稳定高速</strong>的网络，毕竟<strong>Windows 10已经默认使用BBR作为TCP拥塞控制算法</strong>，在<strong>内核版本4.9</strong>以上的Linux上开启BBR也很容易，副作用或许是：如果在上世纪kbps带宽的网络上提供TCP服务，效果可能不如reno，cubic？下面是Linux上开启BBR的方式：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># vim /etc/sysctl.conf  </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">net.core.default_qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fq</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">net.ipv4.tcp_congestion_control</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bbr</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">net.ipv4.tcp_notsent_lowat</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 16384</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># sysctl -p</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>一些实时通信应用也有使用<strong>KCP，QUIC</strong>等不依赖TCP协议的方式来传输数据，其中最大的原因就是<strong>TCP局限性，导致RTT和吞吐量不满足这些应用场景</strong>。HTTP3.0也是基于UDP的QUIC来传输了。估计不久的将来，大学课本就不能继续写HTTP协议是基于TCP的协议了。</p>
<h4 id="net-ipv4-tcp-keepalive-time" tabindex="-1">net.ipv4.tcp_keepalive_time <a class="header-anchor" href="#net-ipv4-tcp-keepalive-time" aria-label="Permalink to &quot;net.ipv4.tcp_keepalive_time&quot;">&ZeroWidthSpace;</a></h4>
<p>默认长连接TCP KeepAlive包是两小时发送一次（7200），Nginx等反向代理服务，可以降低这个参数的值。本身提供长连接的服务比如WebSocket，大多都会有应用层/协议层的保活机制，个人感觉其实没有必要改这个参数。</p>
<h4 id="net-ipv4-ip-forward" tabindex="-1">net.ipv4.ip_forward <a class="header-anchor" href="#net-ipv4-ip-forward" aria-label="Permalink to &quot;net.ipv4.ip_forward&quot;">&ZeroWidthSpace;</a></h4>
<p>ip forward即对IP包做路由，大多数情况下是不需要的，如果被打开了，可以设置为0关闭掉。<strong>多网卡做软路由的场景，则需要打开这个功能</strong>。需要注意：<strong>在Kubernetes集群中，需要打开ip forward功能，否则某些CNI实现会出问题</strong></p>
<h4 id="socket-read-write-memory" tabindex="-1">Socket Read/Write Memory <a class="header-anchor" href="#socket-read-write-memory" aria-label="Permalink to &quot;Socket Read/Write Memory&quot;">&ZeroWidthSpace;</a></h4>
<p>有4个参数控制着Socket发送（Write）、接收（Read）数据的缓冲区大小。这个缓冲区是不分TCP UDP的，TCP在net.ipv4下面也有单独设置缓冲区大小的参数。下面这样可以把缓冲区增大到8MB～16MB，可以视网络状况、应用场景、机器性能来增大缓冲区。</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>net.core.wmem_default = 8388608</span></span>
<span class="line"><span>net.core.rmem_default = 8388608</span></span>
<span class="line"><span>net.core.wmem_max = 16777216</span></span>
<span class="line"><span>net.core.rmem_max = 16777216</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h2 id="其他常用内核参数" tabindex="-1">其他常用内核参数 <a class="header-anchor" href="#其他常用内核参数" aria-label="Permalink to &quot;其他常用内核参数&quot;">&ZeroWidthSpace;</a></h2>
<p>一些特定的应用，会有一些特殊的内参数核要求或优化方案，这里举一些用到过的例子。</p>
<h4 id="vm-max-map-count" tabindex="-1">vm.max_map_count <a class="header-anchor" href="#vm-max-map-count" aria-label="Permalink to &quot;vm.max_map_count&quot;">&ZeroWidthSpace;</a></h4>
<p>具体含义如下：</p>
<blockquote>
<p>This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries.</p>
</blockquote>
<p>这是<strong>ElasticSearch</strong>推荐配置的一个内核参数，因为ES使用<strong>mmapfs，大量分片索引数据需要映射到虚拟内存中</strong>，链接：
<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html</a></p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>vm.max_map_count=262144</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h4 id="fs-file-max-与-ulimit" tabindex="-1">fs.file-max 与 ulimit <a class="header-anchor" href="#fs-file-max-与-ulimit" aria-label="Permalink to &quot;fs.file-max 与 ulimit&quot;">&ZeroWidthSpace;</a></h4>
<p>顾名思义，指的是Linux系统最大能打开的File Descriptor数量，用Windows的话说就是”最大句柄数“。</p>
<p>这个参数<strong>非常常用</strong>，因为Linux下<strong>一切皆文件</strong>，你以为你只是打开了一个TCP连接，虽然不存在读写磁盘文件，但也是要占用文件描述符的！</p>
<p>推荐配置: fs.file-max=65536</p>
<p>这里需要注意此参数和<strong>ulimit里面nofile</strong>的区别：</p>
<p><strong>fs.file-max是操作系统层面的；而ulimit是限制用户级别的</strong>, 二者是需要搭配一起修改的：</p>
<ul>
<li>修改当前登录session的最大打开文件数： “ulimit -n 4096”</li>
<li>持久化修改某用户的最大打开文件数，在 <strong>/etc/security/limits.conf</strong> 添加 “user hard/soft nofile 4096”, nofile意思就是number of open files.</li>
</ul>
<p>注： 不仅是file descriptor, ulimit可以限制的参数很多，比如core dump的大小，虚拟内存的大小，最大进程数等等，“ulimit -a”可以查看详细。</p>
<h4 id="fs-inotify-max-user-watches" tabindex="-1">fs.inotify.max_user_watches <a class="header-anchor" href="#fs-inotify-max-user-watches" aria-label="Permalink to &quot;fs.inotify.max_user_watches&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>可以watch的文件最大数量</strong>，默认值比较小，在开发或使用nodejs相关工具的时候会经常用到，比如：</p>
<ul>
<li><strong>nodemon，webpack</strong>等工具会监听文件变化来Hot reload</li>
<li>使用<strong>chokidar</strong>等文件监听库开发相关应用</li>
</ul>
<p>当然涉及到文件watch都有可能需要在linux下修改此参数，只是<strong>nodejs生态圈对file watch使用更加普遍</strong>，Linux下安装VSCode也会提示最好修改此参数，推荐把这个参数设置大一些：</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>fs.inotify.max_user_watches=524288</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h2 id="在容器环境下的内核参数调整" tabindex="-1">在容器环境下的内核参数调整 <a class="header-anchor" href="#在容器环境下的内核参数调整" aria-label="Permalink to &quot;在容器环境下的内核参数调整&quot;">&ZeroWidthSpace;</a></h2>
<p>上面讲了一些常用的内核参数Tuning的场景和原理，那么问题来了：<strong>如果是运行在容器环境中（docker, containerd 等等），或是在Kubernetes集群部署，有没有什么不同呢</strong>？</p>
<p>笔者做了一些试验，发现网上有很多<strong>错误，片面，不准确</strong>的资料，比如：</p>
<ul>
<li>修改宿主机的内核参数，会带到容器中（<strong>不准确，高版本内核大多数参数修改不生效</strong>）</li>
<li>使用特权容器启动，在InitContainer中直接修改内核参数（<strong>不正确的方案</strong>，特权模式容器以及在容器中产生影响宿主机的副作用，是<strong>不安全，不负责</strong>的办法，即使需要特别的权限，也应该通过<strong>mount和capability等机制</strong>实现）</li>
<li>Kubernetes集群中，在kubelet启动参数添加“--experimental-allowed-unsafe-sysctls”就可以在yaml里面添加修改内核参数的命令了（<strong>不准确</strong>，视版本而定，高版本Kubelet是--allowed-unsafe-sysctls）</li>
</ul>
<p>容器技术并不是传统的虚拟化技术，容器是与宿主机<strong>公用内核</strong>的，因此这个问题并没有那么简单，我们从Docker开始，逐一来看。</p>
<h5 id="ulimit修改" tabindex="-1">ulimit修改 <a class="header-anchor" href="#ulimit修改" aria-label="Permalink to &quot;ulimit修改&quot;">&ZeroWidthSpace;</a></h5>
<ul>
<li>Docker Daemon的启动参数指定 --default-ulimit，可以让后续启动的容器继承</li>
<li>单个Container，run时指定--ulimit参数可以定制ulimit，比如&quot;docker run -d --ulimit nofile=65535&quot;</li>
</ul>
<h5 id="sysctl修改" tabindex="-1">sysctl修改 <a class="header-anchor" href="#sysctl修改" aria-label="Permalink to &quot;sysctl修改&quot;">&ZeroWidthSpace;</a></h5>
<p>sysctl的<strong>最终形态实际上是挂载在 /proc/sys/ 下面的文件 （这也是查看内核参数修改是否生效的直接办法）</strong>，因此对于容器来说，指定sysctl是要覆盖 /proc/sys 的文件的，docker run有 <strong>--sysctl</strong> 参数，可以指定特定的内核参数覆盖到/proc/sys/中。</p>
<blockquote>
<p>Note: Not all sysctls are namespaced. Docker does not support changing sysctls inside of a container that also modify the host system. As the kernel evolves we expect to see more sysctls become namespaced.</p>
</blockquote>
<p>Docker文档中有这么一段话，这就是为什么有人说改宿主机可以继承，有人说必须指定sysctl才可以, <strong>两个说法都是片面的</strong>。翻译概括一下就是：<strong>已经命名空间隔离掉的参数才可以指定，否则继承宿主机</strong>。下面是笔者在几个内核版本的Linux下的测试结果：</p>
<ul>
<li>3.10-4.4版本内核：net.* 参数，宿主机设置，<strong>容器继承生效</strong></li>
<li>3.10-4.4版本内核：容器启动命令指定 --sysctl net.* 参数，<strong>容器启动报错</strong></li>
<li>4.14以上版本内核：net.* 参数，<strong>宿主机设置，容器中无效</strong>，容器指定sysctl后生效</li>
</ul>
<p>内核不停地在更新，笔者没有<strong>挨个版本</strong>试哪个参数在哪个内核版本被Namespaced掉了。个人感觉升级到stable的最新内核版本是安全的，即使是生产环境，追求稳定不代表需要因循守旧, <strong>越新的内核可以隔离配置的sysctl越多</strong>。</p>
<h4 id="插曲-proc目录下的其他文件" tabindex="-1">插曲：/proc目录下的其他文件 <a class="header-anchor" href="#插曲-proc目录下的其他文件" aria-label="Permalink to &quot;插曲：/proc目录下的其他文件&quot;">&ZeroWidthSpace;</a></h4>
<p>说个题外话，这里还有一个类似的问题：<strong>在容器中看到的 /proc/cpuinfo, /proc/meminfo 中CPU、内存信息是容器，还是宿主机的</strong>？</p>
<p>目前，Docker容器中/proc/xxx看到的CPU Memory等仍然是<strong>宿主机的信息</strong>，因为这两个路径并没有挂载文件覆盖。笔者之前遇到过一个问题,在容器环境中，jvm启动如果<strong>不指定Xms, Xmx</strong> (最小，最大堆内存大小)，jvm会默认使用物理内存的四分之一到二分之一，很容易在<strong>限制了总内存大小的容器中</strong>出现<strong>OOM</strong>问题。解决这个问题有两个方案：</p>
<ul>
<li><strong>指定应用使用的内存大小</strong>，不管Runtime是jvm，v8，binary裸奔，都有办法去控制应用本身占用的内存大小</li>
<li><strong>利用挂载修改容器内看到的CPU和内存</strong>： 这里有篇文章，亲测有效<a href="https://medium.com/@Alibaba_Cloud/kubernetes-demystified-using-lxcfs-to-improve-container-resource-visibility-86f48ce20c6" target="_blank" rel="noreferrer">https://medium.com/@Alibaba_Cloud/kubernetes-demystified-using-lxcfs-to-improve-container-resource-visibility-86f48ce20c6</a>。像是美团HULK，阿里Pouch这种自主研发（参考Docker开发）的私有容器技术，做了很多定制和优化，也顺便解决了这个问题。</li>
</ul>
<h4 id="kubernetes集群中内核参数修改" tabindex="-1">Kubernetes集群中内核参数修改 <a class="header-anchor" href="#kubernetes集群中内核参数修改" aria-label="Permalink to &quot;Kubernetes集群中内核参数修改&quot;">&ZeroWidthSpace;</a></h4>
<p>有了上面对Docker的理解，K8S本身作为不关心Container Runtime实现的平台，修改内核参数最终是靠<strong>CRI</strong>的具体实现的，比如Docker。Kubernetes用标准化的方式去声明，<strong>背后调用Docker API和docker run --sysctl 做了一样的事情</strong>。</p>
<p>在Kubernetes集群中，宿主机有几个需要注意的地方：</p>
<ul>
<li>不能关闭ip_forward</li>
<li>不能打开tcp_tw_recycle</li>
<li>不要修改ip_local_port_range （默认情况下，K8S Service的NodePort范围是32000-32767）</li>
<li>目前只能修改 kernel.shm*, kernel.msg*, kernel.sem， fs.mqueue.* , net.*.</li>
</ul>
<p>那么，怎么修改Pod的容器内核参数呢？</p>
<ol>
<li>Kubelet启动参数添加 --allowed-unsafe-sysctls参数 (在<strong>1.11以下的版本这个参数名是 --experimental-allowed-unsafe-sysctls</strong> )，官方文档参考 <a href="https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/" target="_blank" rel="noreferrer">https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/</a></li>
<li>在Pod的Spec定义中，添加需要修改的内核参数键值对，并执行apply命令更新编排信息，修改的地方在 spec.securityContext.sysctls 中，具体如下所示：</li>
</ol>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">apps/v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Deployment</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-deployment</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ns</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-app</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">your-app</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      securityContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        sysctls</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">net.ipv4.tcp_fin_timeout</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"10"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">net.ipv4.tcp_tw_reuse</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      containers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "......"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"......."</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br></div></div><p>如果填写的sysctl name存在非Namespaced参数，启动容器会失败，这时可能会触发BackOff重试，导致<strong>出现大量Failed状态的Pod</strong>，所以在改Yaml之前一定要确认好<strong>Kubelet启动参数是否修改生效</strong>，<strong>内核版本是否支持</strong>该参数Namespace隔离。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本篇浅显地讲了一些内核参数原理和使用经验，Linux的奥妙无穷，通过内核参数管中窥豹就可以学到很多东西。总之，在搞清楚原理和问题根本原因之前，内核参数不能乱改，写好代码才是最关键的。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Geek之路] 树莓派可以用来干什么]]></title>
            <link>https://code2life.top/blog/0047-raspberrypi</link>
            <guid>https://code2life.top/blog/0047-raspberrypi</guid>
            <pubDate>Mon, 06 Jan 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="geek之路-树莓派可以用来干什么" tabindex="-1">[Geek之路] 树莓派可以用来干什么 <a class="header-anchor" href="#geek之路-树莓派可以用来干什么" aria-label="Permalink to &quot;[Geek之路] 树莓派可以用来干什么&quot;">&ZeroWidthSpace;</a></h1>
<p>作为一个伪Geek，我去年下半年看到树莓派4出来之后，小手一抖买了两个。一些朋友问我：树莓派可以用来干啥？本篇就来总结一些树莓派的部分用途，从不同的领域来分析可能的应用场景，主要从四个方向介绍：</p>
<ul>
<li>作为个人电脑的使用场景</li>
<li>作为服务器的使用场景</li>
<li>作为多媒体终端的使用场景</li>
<li>作为物联网设备的使用场景</li>
</ul>
<h2 id="树莓派是什么" tabindex="-1">树莓派是什么 <a class="header-anchor" href="#树莓派是什么" aria-label="Permalink to &quot;树莓派是什么&quot;">&ZeroWidthSpace;</a></h2>
<p>树莓派是一系列为<strong>编程教育</strong>而设计的<strong>只有信用卡大小的微型电脑（单板计算机）</strong>，拥有丰富的硬件接口，能够安装任何<strong>ARM</strong>平台的操作系统，比如Linux的一些发行版、Android、Windows IoT等。下图是当下的一款RaspberryPi 4 Model B（图片来自 <a href="https://www.yahboom.com" target="_blank" rel="noreferrer">https://www.yahboom.com</a>）：</p>
<p><img src="//filecdn.code2life.top/rasp4b.png" alt=""></p>
<p>一台这么多接口的单板计算机，实际只有<strong>掌心大小</strong>（RaspberryPi Zero/ZeroW系列只比大拇指大一点），树莓派3/4系列的四核Cortex A53/A72 CPU功耗低，算力不弱。最重要的是竟然还这么便宜，加上配件也只要3位数的￥！</p>
<h2 id="作为一台个人电脑-树莓派可以" tabindex="-1">作为一台个人电脑，树莓派可以... <a class="header-anchor" href="#作为一台个人电脑-树莓派可以" aria-label="Permalink to &quot;作为一台个人电脑，树莓派可以...&quot;">&ZeroWidthSpace;</a></h2>
<p>树莓派4B出来的时候，某些标题党自媒体宣传“替代PC”，但Linux系统的PC市场份额很低，桌面软件生态不完善，作为PC用途而言，注定只是一个小众选择。我其中一台树莓派4B来当PC用，总体体验不错，下面从一个<strong>软件开发者的使用角度</strong>一一分析。</p>
<h4 id="办公用途" tabindex="-1">办公用途 <a class="header-anchor" href="#办公用途" aria-label="Permalink to &quot;办公用途&quot;">&ZeroWidthSpace;</a></h4>
<p>阿特伍德定律告诉我们：<strong>一切可以用Javascript写的东西最终都会用Javascript写</strong>，推导一下就是：一切以前在<strong>客户端</strong>做的事情，最终都将可以在<strong>浏览器</strong>上做。</p>
<p><strong>各种软件的Web化</strong>，让浏览器成为操作系统之上的“操作系统”，树莓派3B+就可以比较流畅地运行Chromium，树莓派4B表示打开数十个Web页面毫无压力。所以，<strong>能跑浏览器就可以满足大多数普通办公场景</strong>，树莓派CPU和GPU的算力也完全足以应对<strong>简单的</strong>办公场景了。</p>
<p>那么办公用途安装什么系统合适？对于普通终端用户，安卓系统的软件生态则更加繁荣，装Android可以覆盖更多办公娱乐场景（毕竟已经破产锤子TNT工作站已经验证了安卓PC的可行性😆）；专业领域用户装Raspbian则更合适，比如我这样的程序猿。</p>
<h4 id="教育用途" tabindex="-1">教育用途 <a class="header-anchor" href="#教育用途" aria-label="Permalink to &quot;教育用途&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派基金会是一个<strong>慈善组织</strong>，让更多人可以接受到<strong>编程教育</strong>是树莓派的初衷。买一个老少皆宜的树莓派给孩子玩，比去什么<strong>少儿编程</strong>强多了。个人认为<strong>孩子参加少儿编程课程是没有必要的</strong>。计算机科学是建立在基础学科根基上的，而编程语言又是建立在计算机科学上的，<strong>连数学物理基础都没有的低年级孩子学积木式编程，如建空中楼阁</strong>。让孩子玩的开心、锻炼逻辑思维的方式很多，<strong>除非孩子对计算机有极大的兴趣</strong>，否则我不建议低年级孩子刻意参与编程教育课程。</p>
<p>但在树莓派上参与编程和计算机相关教育活动，是有别于现在市面上的课程的，因为玩树莓派学计算机更具有灵活性，Raspbian系统自带各种<strong>真正的编程教育软件</strong>和多种语言的<strong>初级IDE软件</strong>，有兴趣可以自由地深入探索，无兴趣不如<strong>多去读读书或者参加户外活动</strong>。下图是经典的编程教育软件Scratch的截图（Scratch当前版本也已经Web化，甚至无需安装客户端也可以玩，不一定要在树莓派上运行，图片来自Scratch网站 <a href="https://scratch.mit.edu/explore/projects/%EF%BC%89" target="_blank" rel="noreferrer">https://scratch.mit.edu/explore/projects/）</a></p>
<p><img src="//filecdn.code2life.top/scratch.png" alt="//filecdn.code2life.top/scratch.png"></p>
<h4 id="游戏用途" tabindex="-1">游戏用途 <a class="header-anchor" href="#游戏用途" aria-label="Permalink to &quot;游戏用途&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派可以玩Minecraft，也可以运行<strong>复古游戏机模拟器</strong>，比如<strong>RetroPie</strong>（<a href="https://retropie.org.uk" target="_blank" rel="noreferrer">https://retropie.org.uk</a>）。能在树莓派上玩的游戏有限，可能更适合小众动手能力很强的Geek的需求。</p>
<h4 id="编程开发用途" tabindex="-1">编程开发用途 <a class="header-anchor" href="#编程开发用途" aria-label="Permalink to &quot;编程开发用途&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>
<p><strong>Python开发</strong>：<strong>非常棒</strong>，现在Python不仅在机器学习领域是头把交椅，在嵌入式开发也是动态语言第一。树莓派上使用Python的优势在于：</p>
<ul>
<li>树莓派上<strong>Python的硬件开发库</strong>非常完善，甚至很多库都<strong>兼容Arduino上跑micropython</strong></li>
<li>Python本身的跨平台特性，脚本写好放在哪都能跑，比如在树莓派上跑<strong>Tensorflow甚至是PyTorch都可以</strong>！不过在机器学习方面，树莓派的算力就捉襟见肘了。</li>
</ul>
</li>
<li>
<p><strong>Golang开发：差强人意</strong>。能写能跑，但调试不方便，因为Go的Debug工具dlv对ARM的支持2020年才初步解决（<a href="https://github.com/go-delve/delve/issues/118" target="_blank" rel="noreferrer">https://github.com/go-delve/delve/issues/118</a> ）。另一个问题是<strong>Go的主场不在嵌入式开发，硬件相关库兼容性和稳定性不足</strong>。我尝试过Golang的硬件驱动库<a href="https://github.com/google/periph" target="_blank" rel="noreferrer">https://github.com/google/periph</a>，比Python的庞大完善的生态还差很多。</p>
</li>
<li>
<p><strong>JS/TS开发：还不错</strong>。VS Code作为JS/TS，甚至是C# .Net Core开发的最佳工具，既可以本机<strong>直接运行VS Code也可以用VS Code Remote模式</strong>。<strong>大部分常用的VSCode插件</strong>在树莓派上都能正常运行，具体来说：</p>
<ul>
<li><strong>前端开发体验完美</strong>，虽然Webpack构建时间比Intel i7的机子大概慢一倍多，但Hot Reload仍然是秒级，丝毫不影响开发</li>
<li>使用JS/TS开发NodeJS<strong>后端体验完美</strong>，常用的带C++ Binding的npm库也都能在ARM下正常编译</li>
<li><strong>嵌入式开发不够好</strong>，生态不完善，一些硬件模块在npm找不到合适的库，目前rpi-gpio库在树莓派4上有问题，毕竟ECMAScript的主场在大前端领域，不是Python的对手。</li>
</ul>
</li>
</ul>
<h4 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h4>
<p>通过上面几个例子可以看出，作为开发者是可以尝试使用树莓派来替代PC做日常开发的，<strong>主流编程语言及其工具链、三方库大多保持着对ARM平台的兼容性</strong>。中小型项目在开发阶段对计算资源要求并不高，树莓派接上屏幕配个键盘，或是VS Code Remote进行远程开发，都挺好的。当然其<strong>局限性</strong>也不少，比如：ARM Linux下很难做传统C/C++的客户端软件开发；算力有限，大型项目和一些细分领域的专用软件只能用x86平台的中高端PC。</p>
<h2 id="作为一台服务器-树莓派可以" tabindex="-1">作为一台服务器，树莓派可以... <a class="header-anchor" href="#作为一台服务器-树莓派可以" aria-label="Permalink to &quot;作为一台服务器，树莓派可以...&quot;">&ZeroWidthSpace;</a></h2>
<p>树莓派更多的用途是在服务器端。可以只在局域网使用，也可以搭配<strong>DDNS</strong>技术把<strong>树莓派挂到公网上</strong>（一般的企业甚至电信家庭宽带都可以申请到公网IP，或者直接买一台乞丐配置的云服务器也可以拥有公网IP），<strong>不到5W的功耗打造个人云服务器</strong>，一年只要20块钱电费，性价比<strong>极高</strong>。下面来细数一下具体用途。</p>
<p>注：实践这些应用场景，容器化技术是最佳选项，比如先装上<strong>Docker或者Containerd</strong>，再安装各种Docker镜像就可以满足大部分需求。对于多个树莓派<strong>组建集群</strong>的场景，推荐<strong>Rancher的K3S</strong>或者用Docker Swarm，K3S和Kubernetes在使用上几乎感觉不到区别，更轻量但一样强大。</p>
<h4 id="web服务器" tabindex="-1">Web服务器 <a class="header-anchor" href="#web服务器" aria-label="Permalink to &quot;Web服务器&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派的算力部署普通的Web服务绰绰有余了，制约并发能力和延时的的可能会是家庭宽带的<strong>上行带宽</strong>（家庭网络上行带宽远低于下行带宽，300Mbps的下行带宽或许只有30Mbps的上行带宽）。<strong>非高并发和高带宽</strong>要求的服务，完全可以用树莓派搭建服务端，比如：个人网站，App或小程序后台等等（前提是有公网IP，或者DDNS途径把树莓派挂到公网）。树莓派的官方网站据说就是18个树莓派服务器集群组成的。</p>
<p>因为各种<strong>主流的高级语言和开源组件几乎都支持ARM平台</strong>，所以选择熟悉的语言和技术来开发就行了，和在Intel/AMD CPU的Linux上运行差别不大。精简指令集的ARM<strong>低功耗优势在服务端非常明显</strong>，这几年ARM服务器的出现也印证了这一点。</p>
<h4 id="网络代理服务" tabindex="-1">网络代理服务 <a class="header-anchor" href="#网络代理服务" aria-label="Permalink to &quot;网络代理服务&quot;">&ZeroWidthSpace;</a></h4>
<p>在通过内网映射把树莓派挂到公网的同时，反过来看，我们也拥有了一个<strong>部署在局域网的“间谍”</strong>，用这个“间谍”我们可以在公网上访问内网里面的服务，例如“跳板机”，一个最基础的实现可以是：<strong>用FRP ( <a href="https://github.com/fatedier/frp" target="_blank" rel="noreferrer">https://github.com/fatedier/frp</a> )把树莓派的SSH端口映射到某公网IP的X端口，再通过SSH 公网IP的X端口进入树莓派，进而再次用SSH连接内网的其他Linux机器</strong>。</p>
<p>另一方面，除了“间谍”用途（反向代理），树莓派在内网也可以充当<strong>正向代理</strong>的角色，比如作为客户端连个ShadowSocks服务器，再<strong>对局域网提供Socks5代理服务</strong>，整个局域网都可以科学上网（FQ）了。甚至再搭配<a href="//filecdn.code2life.top/ProxifierPE.zip" target="_blank" rel="noreferrer">Proxifier</a>这样优秀的软件，实现对<strong>任意进程透明的科学上网</strong>。</p>
<h4 id="网格计算" tabindex="-1">网格计算 <a class="header-anchor" href="#网格计算" aria-label="Permalink to &quot;网格计算&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>网格计算</strong>是分布式计算的一种形式，举个例子，我们可以用树莓派参与<a href="https://setiathome.berkeley.edu/" target="_blank" rel="noreferrer">SETI@home</a>项目，<strong>帮科学家们搜索外星人</strong>！全世界的志愿者都可以用<strong>算力闲置的设备安装BOINC客户端</strong>参与项目，分析射电望远镜的数据寻找可疑的电磁波。<strong>BOINC</strong>全称是伯克利开放式网络计算平台，现在已经有数十个项目可以参与，如果你不相信有外星人，也可以用BOINC客户端来参与其他更实际的项目，比如我比较喜欢这几个：</p>
<ul>
<li><a href="//boinc.bakerlab.org/" target="_blank" rel="noreferrer">Rosetta@home</a> 预测蛋白质结构和蛋白质设计；</li>
<li><a href="https://lhcathome.web.cern.ch/" target="_blank" rel="noreferrer">lhc@home</a> 帮助CERN的大型强子对撞机模拟粒子的运行。</li>
</ul>
<p><img src="//filecdn.code2life.top/rosetta.png" alt="//filecdn.code2life.top/rosetta.png"><br>
注：一些项目在Windows上运行时会有酷炫的屏保（如上图），动态展现当前计算的状态，Linux下命令行运行<strong>不会</strong>有屏保图片。</p>
<p>我个人服务器上一直用50%的CPU在运行这些项目分摊下来的计算任务，树莓派也可以运行，但相对来说慢一些。我小时候的梦想是从事基础科学的研究，最终却因为种种原因变成了一只程序猿，贡献一些算力也算圆了一丢丢科学梦吧。</p>
<p>同属于分布式计算领域的<strong>区块链</strong>最近比较火，区块链技术其中一个应用就是虚拟货币，俗称挖矿。树莓派也可以挖矿，只是效率比较低。除了网格计算，还有两个云计算衍生的概念：<strong>边缘计算、雾计算</strong>，乍一听云里雾里的，大概意思就是：局域网有一堆传感器和硬件设备数据，找个算力还行的设备先汇总计算一下，做一些在内网就能做的事情。具体到应用，比如作为智能家居的中枢、影音娱乐中心等等，具体细节到下面讲物联网方面再展开。</p>
<h4 id="私有云" tabindex="-1">私有云 <a class="header-anchor" href="#私有云" aria-label="Permalink to &quot;私有云&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>云存储、云办公</strong>等公有云的服务对于大部分人来说已经很方便了，但存在<strong>隐私和数据安全</strong>方面的担忧，高质量的云服务必然是有代价的，最典型的例子就是某度云的网盘<strong>限速</strong>逼着用户充会员。</p>
<p>私有云大多在企业中应用，但其实<strong>每一个人都可以有一朵属于自己的云</strong>，既能像<strong>云盘一样随时随地存取和备份文件，又可以覆盖日常办公大多数场景，如文档/表格/PPT/思维导图/UML图编辑、笔记/便笺、任务规划、日历日程和联系人管理</strong>等等，所有的数据既掌握在自己手里，又可以<strong>任何时间地点访问和同步数据</strong>。这么多强大的功能，竟然在树莓派上部署一个<strong>NextCloud</strong>就全有了！部署这样<strong>一整套私有云</strong>，对熟悉Linux的人来说，完全是<strong>30分钟以内</strong>就能搞定的事情。</p>
<p><strong>NextCloud是我看到过的最酷的PHP项目</strong>。上几个图来感受一下这开箱即用，功能齐全的私有云系统吧：</p>
<p><img src="//filecdn.code2life.top/nextcloud.png" alt="//filecdn.code2life.top/nextcloud.png"></p>
<p>文件存储是核心功能，手机上安装NextCloud的App<strong>实时同步相册，备份联系人，上传下载文件</strong>，是非常实用的功能。导航栏还安装了一些主流的官方和社区的插件，“更多”里面还有：管理个人密码，手机位置追踪等我觉得很不错的插件。另外Office办公套件和Draw.io画图插件也非常棒，社区还有很多优秀的插件来满足各种需求。</p>
<p>其中手机追踪插件（PhoneTrack），需要手机安装PhoneTrack的App上传位置信息，然后就可以画出自己每天的轨迹，甚至包括海拔信息。</p>
<p><img src="//filecdn.code2life.top/phone_track.jpg" alt="//filecdn.code2life.top/phone_track.jpg"></p>
<p>非IT从业者，或者没玩过LNMP技术栈的话，下载<strong>NextCloud Pi系统镜像</strong>写入SD卡，树莓派直接启动就可以<strong>快速体验</strong>NextCloud。关于树莓派支持安装的操作系统，我也整理了一下，<strong>用NextCloud的脑图插件</strong>，画了个思维导图，这个脑图也是在树莓派上画的。</p>
<p><img src="//filecdn.code2life.top/raspi_km.png" alt="//filecdn.code2life.top/raspi_km.png"></p>
<p>私有云的用途写的比较多，因为这是我觉得<strong>这是老少皆宜最实用的防树莓派吃灰的用途</strong>！NextCloud除了强大的插件体系扩展了这个私有云系统的能力，其衍生产品NextCloud Talk甚至还可以用来聊天和语言视频通话。如果不用NextCloud系列搭建私有云，也有其他类似的软件可以尝试。</p>
<p>有人可能会觉得小小的树莓派，怎么可以胜任云存储？几千块钱的<strong>群晖NAS</strong>固然高端大气，但大部分人只是想同步一下手机相册，备份一下数据，偶尔记记笔记画画图，真的需要磁盘冗余阵列，万兆存取速度吗？<strong>用树莓派的USB3.0接个移动硬盘，插上网线</strong>，局域网1000Mbps的速率也足够了，5W功耗吊打专业NAS，写个爬虫脚本放上去运行又能作为一台资源<strong>下载机</strong>。至于公网访问，用DDNS + 免费TLS证书这种方案弄到公网，访问速度也远高于不充会员的百度云盘。</p>
<p>服务端的应用场景就介绍到这里吧，上述的用途还没有用到树莓派大部分硬件接口，下面细说一些跟<strong>硬件</strong>相关的用途。</p>
<h2 id="作为一个多媒体终端-树莓派可以" tabindex="-1">作为一个多媒体终端，树莓派可以... <a class="header-anchor" href="#作为一个多媒体终端-树莓派可以" aria-label="Permalink to &quot;作为一个多媒体终端，树莓派可以...&quot;">&ZeroWidthSpace;</a></h2>
<p>从上面的思维导图看，树莓派有不少<strong>多媒体硬件接口</strong>。音视频的输入输出也可以带来很多应用场景，下面一一介绍。</p>
<h4 id="家庭安防监控" tabindex="-1">家庭安防监控 <a class="header-anchor" href="#家庭安防监控" aria-label="Permalink to &quot;家庭安防监控&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派的<strong>CSI接口接上摄像头</strong>，写个录像脚本就变成了最简易的家庭监控摄像头，进阶一点可以搭建一个<strong>RTMP Server</strong>做实时视频流，想当个主播，把<strong>视频流转到直播平台</strong>就可以了。</p>
<p>只是作为监控摄像头效果肯定不如海康，360，小米等公司的成熟产品，但树莓派的优势在可扩展性，发挥想象力能够DIY出无限的可能性。我感触很深的一个例子是有一个大佬颇有创新的树莓派项目——<strong>共享鱼缸</strong>，用树莓派给自家水族馆直播，观众可以互动投食。详细信息参考：<a href="https://shumeipai.nxez.com/2017/09/27/nature-aquarium-for-sharing.html" target="_blank" rel="noreferrer">https://shumeipai.nxez.com/2017/09/27/nature-aquarium-for-sharing.html</a></p>
<h4 id="家庭影音娱乐中心" tabindex="-1">家庭影音娱乐中心 <a class="header-anchor" href="#家庭影音娱乐中心" aria-label="Permalink to &quot;家庭影音娱乐中心&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派加上<strong>一根HDMI线连到电视</strong>上，就是一个机顶盒，搭配<strong>OpenELEC/Kodi系统</strong>，当一个<strong>家庭多媒体中心</strong>也是不错的选择。但现在版权控制非常严格，即使OpenELEC虽然很优秀，又有丰富的插件，但新的影视资源并不好找，各家网络视频公司和运营商也是封闭式的发展各自的App和会员系统，他们拿到版权并不想把影视资源付费开放出去。如果有资源的话，把这些影视资源用装了OpenELEC系统的树莓派管理起来，是能够代替家里的机顶盒的，无需忍受各种广告。</p>
<h4 id="音视频通信系统" tabindex="-1">音视频通信系统 <a class="header-anchor" href="#音视频通信系统" aria-label="Permalink to &quot;音视频通信系统&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>拇指大的树莓派ZERO</strong>就足够扩展成一个小<strong>电话</strong>了，之前也看到过有Geek把树莓派电话做成了一个真正的产品。我尝试过编译<strong>BareSIP</strong>（一个C语言写的SIP终端软件）把树莓派变成一个<strong>视频会议设备</strong>，开<strong>Zoom会议</strong>或者其他支持<strong>SIP/H.323协议接入视频会议</strong>是没有问题的，会议期间CPU使用率仅有25%，音视频也很流畅，缺点是裸露的3.5mm耳机接口<strong>杂音比较大</strong>。对于开发者来说可以二次开发来解决问题，甚至变成真正的商用产品，也有人这么做了。</p>
<p>对于个人用户来说，Raspbian客户端软件的生态不齐全，比如没有ARM Linux版本的Zoom客户端，只能间接通过SIP终端接入（前提是需要买Zoom的Conference Room Connector的服务），如果是装安卓系统来实现音视频通信可能更方便一些。</p>
<p><img src="//filecdn.code2life.top/rpi-meeting.png" alt="//filecdn.code2life.top/rpi-meeting.png"><br>
注：上图为树莓派上BareSIP拨号后的输出，可以配置GPU对视频流硬件编码，帧率还不错。</p>
<h2 id="作为物联网设备-树莓派可以" tabindex="-1">作为物联网设备，树莓派可以... <a class="header-anchor" href="#作为物联网设备-树莓派可以" aria-label="Permalink to &quot;作为物联网设备，树莓派可以...&quot;">&ZeroWidthSpace;</a></h2>
<p>上面的用途，基本都不会用到那40Pin引脚，而<strong>树莓派的<a href="https://pinout.xyz/" target="_blank" rel="noreferrer">40Pin引脚</a>，是它变身物联网设备和边缘计算中枢的精髓所在</strong>。利用树莓派的<strong>通用输入输出</strong>（GPIO）能力，可能会找到是最富有创新点和最具有Geek范儿的用途，做无人机、做机器人、做智能音箱，还有无数的可能性等待发掘和探索。</p>
<h4 id="从基础的电子积木开始——硬件模块控制中心" tabindex="-1">从基础的电子积木开始——硬件模块控制中心 <a class="header-anchor" href="#从基础的电子积木开始——硬件模块控制中心" aria-label="Permalink to &quot;从基础的电子积木开始——硬件模块控制中心&quot;">&ZeroWidthSpace;</a></h4>
<p>树莓派GPIO引脚<strong>直连传感器</strong>或者其他硬件模块，或是<strong>用UART等方式连上其他的MCU</strong>。树莓派收集到数据，做一些处理和控制逻辑，再做一些云端同步和消息推送之类的，就可以告诉别人实现“雾运算”了。</p>
<p>简单的搭积木式地组合一些硬件，是入门嵌入式开发的一个方法，如果具体到一些的实际场景，可以衍生出各种应用，比如<strong>门禁系统、3D打印机监控和控制组件、无人机图传组件等等</strong>，都可以基于树莓派去做。</p>
<h4 id="智能小车" tabindex="-1">智能小车 <a class="header-anchor" href="#智能小车" aria-label="Permalink to &quot;智能小车&quot;">&ZeroWidthSpace;</a></h4>
<p>智能小车很多电子电气专业的同学在学校都做过，基础的功能有避障、测距、循迹、遥控等等。51系列单片机的例子比较多，而<strong>树莓派加个底盘、轮胎，连上传感器、马达、电源</strong>，也能摇身一变智能小车，网上可以找到很多树莓派小车的案例。</p>
<p>简单的智能小车显然实用价值不高，发散思考一下，放大一些可以做智能儿童玩具车，装上刷子可以做扫地机器人，配上室内定位系统可以变成一个智能仓储的运输车。不过<strong>工程化研发和量产</strong>是有很多挑战的，实际上真正的电动玩具车和扫地机器人也不可能用树莓派作为MCU，但这并不妨碍我们拿树莓派去学习、探索和尝试。</p>
<h4 id="机器人、无人机" tabindex="-1">机器人、无人机 <a class="header-anchor" href="#机器人、无人机" aria-label="Permalink to &quot;机器人、无人机&quot;">&ZeroWidthSpace;</a></h4>
<p>这两个产业算是IT技术的集大成者，涉及多个学科的前沿科技，技术含量和门槛都很高，但也有一些大佬用树莓派做成了<strong>简单的机器人、无人机</strong>的。这里说的机器人并不是简单遥控的“伪机器人”，而是说具有<strong>感知能力</strong>甚至<strong>人工智能</strong>的产品。比如斯坦福机器人俱乐部的四足机器人项目：</p>
<ul>
<li><strong>DogGo</strong>：斯坦福大牛们创造的开源四足机器人，使用的MCU是Teensy系列的板子 <a href="https://github.com/Nate711/StanfordDoggoProject" target="_blank" rel="noreferrer">https://github.com/Nate711/StanfordDoggoProject</a></li>
<li><strong>Pupper/Woofer</strong>：斯坦福机器人俱乐部的<strong>二代目</strong>四足机器人，使用的MCU正是<strong>树莓派ZERO</strong> <a href="https://github.com/stanfordroboticsclub/StanfordQuadruped" target="_blank" rel="noreferrer">https://github.com/stanfordroboticsclub/StanfordQuadruped</a></li>
</ul>
<p>关于无人机，我一直有个梦想是组装一个自己的四轴无人机（目前还没有提上日程）。无人机最关键的部分是<strong>飞行控制系统</strong>，简称飞控，Geek们用的大多是当前最流行的开源飞控平台<a href="https://github.com/ArduPilot/ardupilot" target="_blank" rel="noreferrer">APM</a>。<strong>APM发源于Arduino，目前也支持Linux运行</strong>，因此树莓派也是可以运行APM飞控的的。另一个开源飞控PX4，适用于Pixhawk的硬件，直接跑在树莓派Linux系统似乎不太方便。</p>
<p>直接拿树莓派跑飞控做无人机难度还是比较高的，简单的途径是用树莓派做图传，<strong>飞控用专用模块</strong>，积木式拼接来组装一个无人机。当然，要真正创造它们不仅需要软硬件开发能力和很强的动手能力，深究下去还需要数学、物理以及衍生学科的知识作为理论基础。</p>
<p>更进一步，更高级的<strong>感知和预测等AI</strong>能力离不开<strong>机器学习</strong>，在这方面也有一些可以在<strong>树莓派上跑的开源机器学习平台</strong>，比如：</p>
<ul>
<li>Tensorflow Lite：Tensorflow的轻量化版本，适用于边缘计算，可以用来在树莓派上训练模型，实现简单的人工智能应用</li>
<li>Pytorch：Facebook的开源机器学习库，最近比较火，也能在树莓派上跑起来</li>
</ul>
<p>最直接的例子是有Geek拿树莓派玩<strong>自动驾驶</strong>，有一些是基于<strong>Donkey Car</strong>项目开发的（Donkey Car是一个基于Tensorflow的开源自动驾驶项目，训练过程需要在高性能GPU上进行，树莓派算力不够）<a href="https://github.com/autorope/donkeycar" target="_blank" rel="noreferrer">https://github.com/autorope/donkeycar</a></p>
<h4 id="智能音箱" tabindex="-1">智能音箱 <a class="header-anchor" href="#智能音箱" aria-label="Permalink to &quot;智能音箱&quot;">&ZeroWidthSpace;</a></h4>
<p>2014年亚马逊Echo发布之后，智能音箱产业蓬勃发展，2020年已是一片红海，占据国内大部分市场的只剩小米的小爱音箱，天猫精灵，百度小度这几个终端产品了。智能音箱这么火，我们是不是可以打造一个自己的智能音箱呢？其实智能音箱的最核心的几项技术：<strong>语音唤醒、语音识别、自然语言处理/语义理解、语言播报</strong>，各大厂开发者平台都提供了API，要自己打造一个“没有核心技术”的智能音箱，做<strong>API的集成</strong>就可以了，毕竟只有技术和数据方面的大厂才有可能拥有智能语音和NLP领域最好的模型。</p>
<p>如果用树莓派做智能音箱的话，软件方面可以选择开源项目<strong>Wukong Robot（<a href="https://github.com/wzpan/wukong-robot" target="_blank" rel="noreferrer">https://github.com/wzpan/wukong-robot</a>）</strong>，代码清晰，并且已经实现了很多服务API的集成；硬件方面最好用<strong>麦克风阵列</strong>获取音频输入，推荐ReSpeaker 4麦克风阵列扩展板，<strong>远场识别</strong>效果还是不错的。</p>
<p>我拿其中一个树莓派4 + ReSpeaker 做了个“智障”音箱，另外装了DLNA的服务器和客户端(minidlna和gmediarender)，手机可以连上去放音乐，但感觉相比专业的智能音箱差别还是太大了：</p>
<ul>
<li>一个是<strong>唤醒灵敏度和准确性</strong>的问题，这个问题我家小爱音箱也比较严重，经常<strong>误唤醒</strong></li>
<li>另一个是<strong>延迟和内容质量</strong>问题，集成<strong>第三方API</strong>比大厂音箱自己<strong>原生支持</strong>要差了一个级别，无论是响应时间，还是准确度和回答内容的质量，都比不上真正的智能音箱，而且个人无法得到优质内容的版权也是个问题。</li>
</ul>
<p>当然，<strong>专业和业余的区别</strong>就在这里，虽然质量比不上，但DIY的优势在于<strong>灵活性</strong>，想集成什么只要开发代码就可以了。</p>
<h4 id="智能家居中枢" tabindex="-1">智能家居中枢 <a class="header-anchor" href="#智能家居中枢" aria-label="Permalink to &quot;智能家居中枢&quot;">&ZeroWidthSpace;</a></h4>
<p>要在自己家里部署智能家居，除了购买成熟的解决方案，比如小米系智能家居产品或者天猫精灵系的产品以外，还有个办法就是<strong>DIY智能家居</strong>。DIY有什么好处呢？</p>
<ul>
<li>买带有智能功能的家电时，不用管这个品牌是站队了小米系还是站队了天猫系，还是自成一体要下载单独App的，开源社区有方案就用，没方案逆向工程搞起</li>
<li>实现不同品牌产品<strong>互通互联</strong>，自己定制非常灵活的自动化方案，无需受限于米家等App的功能</li>
<li><strong>便宜</strong>，可以选用性价比更高的产品，小米生态链的智能家居产品性价比参差不齐，并不都是高性价比的产品</li>
<li>有捣腾的乐趣，可以<strong>扩展</strong>自己想要的功能，比如易微联的智能开关甚至可以拆开给ESP模块烧录自己写的固件</li>
</ul>
<p>那么DIY智能家居具体要做什么呢？</p>
<p>除了<strong>选购或改造电子电器产品</strong>外，还需要有一个<strong>智能家居中枢</strong>负责所有硬件的控制和状态同步，以及自动化的实现。这个智能家居中枢控制系统的角色，树莓派完全可以担任，</p>
<ul>
<li>如果想多写一些代码，可以考虑基于<strong>NodeRed</strong>做</li>
<li>不想写太多代码的话，<strong>Home Assistant</strong>是不二之选</li>
</ul>
<p><strong>Home Assistant</strong>是一个Python的开源智能家居项目，功能非常强大，扩展性也很不错，社区活跃程度极高，<strong>已经实现了很多主流家居产品的集成</strong>。用Home Assistant能实现打通各种主流家电品牌的智能或非智能产品（非智能的传统产品通过红外控制等手段，也可以<strong>转变成为智能产品</strong>）。试想一下让iOS的Siri助理开一个十年前的电视机，或是让天猫精灵控制米家的风扇，是不是很激(ji)动(lei)？</p>
<p>要在树莓派上运行Home Assistant（或者其他地方，比如NAS上），用<strong>Hass.io</strong>部署更方便。Hass.io是包括了Home Assistant以及周边组件的一套东西的合集，另外，树莓派直接刷Hassbian系统也可以达到相同的效果。</p>
<p>运行成功之后需要将所有智能产品作为实体，<strong>通过Yaml的方式配置到系统中</strong>，再<strong>配置UI</strong>、添加一些<strong>自动化规则</strong>。</p>
<p>Home Assistant有一定的学习成本，我也只尝试了一些插件，学习了简单的流程，折腾过ESPHome并且添加了ESP8266系列的开关模块，但没有真正把家里改造一番。下图分别是一个卡片布局的Hass系统UI，以及一个配置了Floorplan的UI，平板装到墙上统一可视化控制，非常酷炫（图片来自网络，没找到真正的原创是谁）。</p>
<p><img src="https://filecdn.code2life.top/hass-ui-example.png" alt="https://filecdn.code2life.top/hass-ui-example.png"></p>
<p><img src="https://filecdn.code2life.top/hass-floorplan.png" alt="https://filecdn.code2life.top/hass-floorplan.png"></p>
<h2 id="其他类似产品" tabindex="-1">其他类似产品 <a class="header-anchor" href="#其他类似产品" aria-label="Permalink to &quot;其他类似产品&quot;">&ZeroWidthSpace;</a></h2>
<p>除了树莓派系列，Geek们常玩的还有一些<strong>AVR（Arduino系列）、ESP（ESP8266系列）、STC系列（89C51,89C52等）单片机</strong>。但这些板子的算力远低于树莓派这样的<strong>单板计算机</strong>，而下面介绍的是一些算力与树莓派相近的产品。</p>
<ul>
<li><strong>Nano Pi系列</strong>：也是一系列小巧的单板计算机，性价比也很高，不同配置的型号很多，有兴趣可以试一试</li>
<li><strong>BananaPi/OrangePi</strong>：俗称香蕉派/香橙派，兼（shan）容（zhai）树莓派的开源硬件产品（<strong>树莓派硬件并不开源</strong>），据说稳定性不如树莓派，目前4代树莓派4核Cortex-A72 CPU的性能成倍提升，感觉这些板子几乎没有性价比优势了</li>
<li><strong>Rock Pi</strong>及基于<strong>瑞芯微RK</strong>系列芯片的开发板：主打高性能和音视频处理能力，国内大多数<strong>机顶盒和智能电视</strong>都是RK系列芯片，RK3288、RK3399等等ARM芯片，相关的单板计算机产品性能是超过树莓派的，而且主板大多早已支持双摄、双4K视频输出，搭配安卓系统在生态上非常成熟。当然缺点就是价格和配件也稍贵一些。</li>
<li><strong>Intel Edison/Galileo/Joule/Curie</strong>：英特尔的x86嵌入式板子，我有一块曾经在英特尔的好朋友赠送的Edison板子，做工精致，400MHz的双核Quark处理器，虽然算力有限，但是能直接跑x86的二进制程序，硬件接口兼容Arduino，是好东西，但价格不菲，而且前几代已经停产。</li>
<li>其他小众板子，大多数跑Linux的都ARM架构的，比如华硕<strong>ThinkBoard、荔枝派</strong>等等，也有例外，比如某宝上还有卖国产RISC-V架构的平头哥芯片开发板。</li>
</ul>
<p>树莓派的博通SoC中的CPU是ARM架构的，其他同算力级别的单板计算机，CPU也大多是基于ARM的，少数是基于x86架构的芯片。</p>
<p>精简指令集（RISC）的ARM架构芯片，通常比复杂指令集（CISC）的x86架构芯片功耗低很多，这对<strong>物联网和移动端</strong>，乃至服务端都至关重要，除非以后电池技术出现革命性发展。这也是我们日常生活中各种电子产品，ARM芯片的身影越来越多的原因。最近苹果甚至宣布了一个新闻：以后<strong>Mac系列的PC和笔记本电脑也要用ARM芯片</strong>了！</p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>以上列举的所有树莓派的使用场景，大部分是从<strong>网络上搜集到的</strong>，都是<strong>有人已经做过的</strong>，我结合了一些个人经验和理解整理介绍了出来。这些也只是无数极客们探索尝试过的一小部分。上面的介绍涉及了很多领域，但树莓派一定还有很多未被发现的用途。</p>
<p>树莓派作为一台Linux计算机，在软件开发方面就有很多应用场景；而硬件和嵌入式开发方面，即使不了解数电、模电等这些电子专业的知识，我们也可以用树莓派加上现成的模块，搭一搭简单的电子积木。站在巨人肩膀上，让<strong>半导体中电子的转移</strong>按照我们想要的方式，变成声音、电磁波、热量、动能，或是感知这个世界，岂不是一件极其有趣的事情？</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Spring Events泛型使用方法]]></title>
            <link>https://code2life.top/blog/0046-spring-event</link>
            <guid>https://code2life.top/blog/0046-spring-event</guid>
            <pubDate>Thu, 31 Oct 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="spring-events泛型使用方法" tabindex="-1">Spring Events泛型使用方法 <a class="header-anchor" href="#spring-events泛型使用方法" aria-label="Permalink to &quot;Spring Events泛型使用方法&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>发布-订阅模式</strong>作为一种解耦业务的常用手段，在分布式系统中通常结合消息队列组件实现。然而<strong>单体系统</strong>中，实现发布订阅模式则需要一个<strong>应用程序内部的事件总线</strong>，比如前端Vue中用一个全局的Vue对象来做事件总线，后端拿Java来说，比较常用的是<strong>Spring Events</strong>，这里简记一下Spring Events的原理和用法。</p>
<h2 id="从类型擦除说起" tabindex="-1">从类型擦除说起 <a class="header-anchor" href="#从类型擦除说起" aria-label="Permalink to &quot;从类型擦除说起&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>泛型</strong>，作为Java1.5之后一个常用特性，是消灭重复代码一大利器。但泛型其实是语法糖，在<strong>编译期解糖即擦除了类型信息</strong>，比如一个ArrayList&lt;String&gt;和ArrayList&lt;Integer&gt;的真正类型在运行期其实是<strong>完全一样</strong>的。</p>
<p>不过泛型类型擦除与今天的主题Spring Events有什么关系呢？试想一下，一个事件总线上，一定是会有<strong>不同类型的事件</strong>发生的，不同的事件类型有一些共性。如果是定义不带泛型的事件（类似UpdateUserEvent，DeleteProductEvent这样），必定出现大量雷同的代码；但如果定义了一个通用的<strong>带泛型的事件类型</strong>，比如像下面这样的MutationEvent，由于类型擦除的存在，会导致<strong>Event无法按照真正的内部对象类型来分发事件</strong>：</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lombok.Getter;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MutationEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Getter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> T source;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Getter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> MutationType type; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Created / Updated / Deleted</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MutationEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(T </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">，MutationType </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.source </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> data;</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.type </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> type;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><p>用传统的方式如何实现按事件类型监听Event呢？下面是一个典型的例子</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.context.ApplicationListener;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.context.event.ContextClosedEvent;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyEventHandler</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> implements</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ApplicationListener</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">ContextClosedEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onApplicationEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ContextClosedEvent </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">event</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // do something when application closed</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>这里监听了一个 <strong>ContextClosedEvent</strong> 来<strong>在Spring容器销毁时做一些资源释放的工作</strong>，实现一个<strong>ApplicationListener</strong>接口，指定监听特定类型的事件。于是当ContextClosedEvent发生时，Spring就会调用实现注册好的MyEventHandler的onApplicationEvent方法，实现发布订阅。</p>
<p>在Spring4.2之后，Spring Events有很多改进和新功能，比如<strong>注解方式注册监听器，泛型支持，事务支持</strong>等等。于是我们可以利用这点来解决上面所说的泛型类型擦除问题，用一种新的方式来实现发布订阅。</p>
<h2 id="进化-带泛型的spring-events" tabindex="-1">进化: 带泛型的Spring Events <a class="header-anchor" href="#进化-带泛型的spring-events" aria-label="Permalink to &quot;进化: 带泛型的Spring Events&quot;">&ZeroWidthSpace;</a></h2>
<p>因为类型擦除的存在，我们不能指望上面定义的MutationEvent&lt;T&gt;可以按照真正的类型 (T) 分发到不同的监听器上，但新版本的Spring提供了一个巧妙的办法，把<strong>真正的类型带到运行期</strong> —— 实现 <strong>ResolvableTypeProvider 接口</strong>。我们稍微改造一下刚才的MutationEvent类，让MutationEvent可以按照 T 的真正类型来分发到EventListener中：</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lombok.Getter;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.core.ResolvableType;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.core.ResolvableTypeProvider;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MutationEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">implements</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ResolvableTypeProvider</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Getter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> T source;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Getter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> MutationType type; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Created / Updated / Deleted</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MutationEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(T </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">，MutationType </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.source </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> data;</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.type </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> type;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ResolvableType </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getResolvableType</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ResolvableType.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">forClassWithGenerics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getClass</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                ResolvableType.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">forInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(source));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><p>这里我们调用了ResolvableType.forClassWithGenerics，然后用source这个真正的T类型对象实例的类型，来返回给Spring事件分发器，这样真正的类型就在运行期被动态塞入分发器了。Spring实现Event分发的源码在<strong>ApplicationListenerMethodAdapter.java的processEvent方法</strong>中，其中<strong>调用resolveArguments时就会调用event的getResolvableType方法</strong>来作为分发判断条件之一。这里截取了Spring源码中对于事件分发的关键代码之一：</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// org.springframework.context.event.ApplicationListenerMethodAdapter</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> processEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ApplicationEvent event) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // resolveArguments里调用 getResolvableType(event);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> resolveArguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(event); </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">shouldHandle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(event，args)) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    Object result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> doInvoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(args);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      handleResult</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(result);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      logger.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">trace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"No result object given - no result to handle"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>除了泛型支持以外，<strong>还有@EventListener注解</strong>的支持，无需再配置繁琐的xml了，处理事件的代码可以是这样的：</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.context.event.EventListener;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.stereotype.Component;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Component</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UserDataChangedHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">EventListener</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> userDataChanged</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MutationEvent&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">User</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">event</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      // business logic ...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p><strong>@EventListener</strong>注解有两个参数: <strong>classes，condition</strong>。classes用于指定监听类型， 这里没有声明则默认监听MutationEvent，另外condition是用<strong>SpringEL来通过表达式动态过滤事件</strong>，个人感觉比较鸡肋，用了泛型事件之后，同一大类的事件不太可能会有重复的事件监听逻辑，过滤完全可以在代码里面做，比SpringEL不知道快到哪里去了。静态类型语言就该有静态类型的样子，尽量不要整那么多动态的东西，性能差还不好调试。不扯远了，Publish一个事件的代码是这样的:</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Autowired</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ApplicationEventPublisher publisher;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// in some method</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">publisher.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">publishEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> MutationEvent&#x3C;>(newUser，MutationType.CREATE));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">publisher.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">publishEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> MutationEvent&#x3C;>(deletedUser，MutationType.DELETE));</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>在<strong>publishEvent</strong>这一行，IDEA会有一个<strong>小耳机夹着豆子</strong>的图标，告诉你这是一个Event，点一下会自动识别定位到事件监听器的方法里面，还是很好用的。</p>
<h4 id="灵魂拷问-为什么需要发布订阅模式" tabindex="-1">灵魂拷问: 为什么需要发布订阅模式 ? <a class="header-anchor" href="#灵魂拷问-为什么需要发布订阅模式" aria-label="Permalink to &quot;灵魂拷问: 为什么需要发布订阅模式 ?&quot;">&ZeroWidthSpace;</a></h4>
<p>至此，我们已经利用Spring实现了一个最简单的发布订阅， 也许会有同学想问，为什么要搞这么复杂，这些EventHandler业务逻辑<strong>直接写在数据库操作语句之后</strong>不行么？要回答这个问题，我们先总结一下这些<strong>EventHandler的使用场景</strong></p>
<ul>
<li>数据变化之后<strong>清除缓存</strong> (这是一种比较常见的缓存更新方式，简单可靠，只有在清除失败，或数据库主从同步间隙被脏读才有可能出现缓存脏数据)</li>
<li><strong>发送消息</strong>告诉下游系统，比如往消息队列推送消息</li>
<li><strong>更细粒度但无关核心逻辑的切面操作</strong>，比如<strong>异步任务的触发，监控，审计</strong>等等。因为Event的<strong>参数可以任意改变，比AOP的切面编程更加灵活</strong></li>
<li>对数据变化的<strong>反应式处理</strong>，实现更加<strong>Reactive</strong>的逻辑，例如实现<strong>长活事务Sagas</strong>，分布式事务<strong>发起，协调，补偿</strong>等等 (Sagas类似乐观锁的思路，在事务失败时补偿，而不是2PC/3PC/TCC这种悲观锁思维的分布式事务)</li>
</ul>
<p>这些用法都有一个共同点，<strong>与核心业务关系不密切，而且具备一定的普适性</strong>。比如<strong>更新用户信息</strong>可能在<strong>多处业务代码中</strong>都会有，而UserService<strong>不应该</strong>依赖诸如CacheService，NotificationService这些组件，因此用一个EventHub来解耦这类逻辑再适合不过了。牢记<strong>单一职责原则</strong>，知道一个类该干什么不该干什么，是OOP的关键点之一。另外不直接用<strong>观察者模式</strong>，而是引入一个带有<strong>事件中心的发布订阅模式</strong>，也是为了让事件<strong>产生者和消费者再次解耦</strong>，否则事件的<strong>广播，过滤</strong>等等操作就比较麻烦了。</p>
<h2 id="事务和异步化处理" tabindex="-1">事务和异步化处理 <a class="header-anchor" href="#事务和异步化处理" aria-label="Permalink to &quot;事务和异步化处理&quot;">&ZeroWidthSpace;</a></h2>
<p>Spring4.2对Spring Events的增强中，还有对事务的支持 <strong>@TransactionalEventListener</strong> ，这个注解可以用于配置在何时执行EventHandler，如果没有事务的话，默认不执行任何监听器，除非<strong>fallbackExecutor</strong>置为true，有4个阶段可以声明事件监听器，这用来做数据库的<strong>事务监控</strong>非常合适。</p>
<ul>
<li>BEFORE_COMMIT</li>
<li>AFTER_COMMIT</li>
<li>AFTER_ROLLBACK</li>
<li>AFTER_COMPLETION</li>
</ul>
<p>阅读Spring源码可以看出它只是<strong>解耦了同步调用</strong>，比如在事务中publish一个event，但是在<strong>处理逻辑中抛异常，会导致整个事务回滚</strong>，因此很多场景中我们需要对Event处理<strong>异步化</strong>。传统的方式则是预先定义一个线程池，提交任务等待调度即可，或者也可以<strong>用@Async注解</strong>，直接加到EventListener上面实现异步化。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Async</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">EventListener</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> userDataChanged</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(MutationEvent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">User</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> event) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>@Async的本质也是一个预定义的线程池，在使用@Async之前，需要在<strong>SpringBoot启动类或配置类添加@EnableAsync注解，最好再自定义一个线程池</strong>，比如下面这样的：</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.scheduling.annotation.AsyncConfigurer;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.springframework.stereotype.Component;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> com.google.common.util.concurrent.ThreadFactoryBuilder;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.util.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.util.concurrent.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Component</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyAsyncConfigurer</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> implements</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> AsyncConfigurer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Executor </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getAsyncExecutor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ExecutorService taskThreadPool </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ThreadPoolExecutor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">                8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">，</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">32</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">，</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">300</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">，TimeUnit.SECONDS,</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">                new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> LinkedBlockingQueue&#x3C;>(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)，</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                new ThreadFactoryBuilder().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setNameFormat</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"my-task-%d"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">                new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ThreadPoolExecutor.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CallerRunsPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> taskThreadPool;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Override</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> AsyncUncaughtExceptionHandler </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getAsyncUncaughtExceptionHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyAsyncExceptionHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br></div></div><h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>写这篇杂技文章的原因是看到某些历史代码存在大量重复或类似的Spring Events逻辑，上百个Event类的定义，然后还有一个巨长的<strong>数千行EventHelper.java文件</strong>。虽然笔者之前没有使用过，但任何人一看到这样的代码，感觉就不是优雅的解决方案。于是看了一些文档和源码，在另一个项目中用泛型Event和更统一的事件处理逻辑，让类似的逻辑更清晰了一些。Java生态圈<strong>即使发展缓慢，新的技术也层出不穷，也不乏非常值得学习的东西</strong>，小到几个注解，大到新的框架和平台。<strong>不做安居一隅因循守旧的开发者</strong>.</p>
<p>参考链接:</p>
<ul>
<li><a href="https://www.baeldung.com/spring-events" target="_blank" rel="noreferrer">https://www.baeldung.com/spring-events</a></li>
<li><a href="https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2" target="_blank" rel="noreferrer">https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2</a></li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[朝花夕拾] 游戏开发之粒子系统]]></title>
            <link>https://code2life.top/blog/0045-particle-system</link>
            <guid>https://code2life.top/blog/0045-particle-system</guid>
            <pubDate>Sun, 16 Jun 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="朝花夕拾-游戏开发之粒子系统" tabindex="-1">[朝花夕拾] 游戏开发之粒子系统 <a class="header-anchor" href="#朝花夕拾-游戏开发之粒子系统" aria-label="Permalink to &quot;[朝花夕拾] 游戏开发之粒子系统&quot;">&ZeroWidthSpace;</a></h1>
<p>笔者大学时出于兴趣，组团做过一些简单的游戏开发，有参加比赛的，也有课程作业的。时过境迁，毕业之后再也没有碰过游戏开发，如今整理旧电脑想起一些往事，算是朝花夕拾吧。</p>
<h2 id="讲讲故事" tabindex="-1">讲讲故事 <a class="header-anchor" href="#讲讲故事" aria-label="Permalink to &quot;讲讲故事&quot;">&ZeroWidthSpace;</a></h2>
<p>大二一次课程作业是组队开发PC版的对战协作消消乐，因为团队成员都熟悉的语言是Java，而Swing又不合适做游戏，最终就用JavaFx做了。</p>
<p>其实JavaFx或者Java本身也不太适合做游戏，粒子系统也没有现成的轮子可以用。当时想自己造个用在爆炸特效、背景特效上面，但是时间太紧张，就用图片帧切换实现了一些简单的效果，不过也打开了探索游戏开发的大门。（PS：回头看5年前的代码，竟然是MVVM模型，小伙伴们太给力了）</p>
<p><img src="//filecdn.code2life.top/star_light.gif" alt=""></p>
<p>经过项目实战，JavaFx玩的挺熟了，2014年七夕给女朋友（<strong>现在是笔者的妻子以及孩子的母亲</strong>）做了个七夕表白程序，中间有个撒花特效，自己写了个<strong>非常简单的粒子特效。粒子发射器随机生成玫瑰花，每个粒子（花）有一个下落的行为，一个粒子管理器控制生命周期</strong>，这部分只有几百行代码，效果如下图。</p>
<p><img src="//filecdn.code2life.top/tanabata.gif" alt=""></p>
<p>现在想想还是挺惭愧的，曾经有不少次给家人和自己做点软件的想法，但业余时间总是在做各种各样其他的事情，即使写代码也大多是学习或是开发与业务相关的东西。</p>
<p>遍身罗绮者，不是养蚕人，或许就是这个道理吧。</p>
<h2 id="浅入浅出粒子系统" tabindex="-1">浅入浅出粒子系统 <a class="header-anchor" href="#浅入浅出粒子系统" aria-label="Permalink to &quot;浅入浅出粒子系统&quot;">&ZeroWidthSpace;</a></h2>
<p>笔者不是专业的游戏开发，所以只能讲一些浅显的概念和原理，以及基本的使用方法。</p>
<h4 id="粒子系统简介" tabindex="-1">粒子系统简介 <a class="header-anchor" href="#粒子系统简介" aria-label="Permalink to &quot;粒子系统简介&quot;">&ZeroWidthSpace;</a></h4>
<p>通过上面的故事，可以总结一下，粒子系统的核心是<strong>粒子发射器生成的一个个行为独立</strong>的粒子，共同构建出动画。粒子系统使用大量很小的的模型/图片或图形，通常来<strong>模拟不精确的效果，或是混乱的系统</strong>。使用场景不限于：</p>
<ul>
<li>游戏开发，模拟现实世界中诸如火焰，雨雪雾尘等等</li>
<li>网站背景特效或者前端动画，相对简单和抽象</li>
<li>复杂逼真的3D粒子动画渲染甚至可以用在电影特效中</li>
</ul>
<p>那么怎么实现一个粒子系统呢？其实粒子系统的<strong>每一帧</strong>动画，或者延伸到游戏开发的本质，就是把每一帧分为<strong>模拟计算阶段、画面渲染阶段</strong>，在粒子系统中：</p>
<ul>
<li><strong>模拟阶段</strong>：根据参数设置，计算粒子的生成与销毁，处理单个粒子行为。比如发射器需要在哪里发射多少个粒子？哪些粒子已经过期或超出总数了？对于单个粒子是要下落，还是旋转跳跃？单个粒子的下一个颜色和透明度是什么？</li>
<li><strong>渲染阶段</strong>：上面的模拟阶段已经全部计算好了下一帧怎么展现，那么<strong>渲染阶段就是拿起画笔在2D或者3D画布上，把虚拟的状态数据临摹出来</strong>。</li>
</ul>
<p>由于粒子的可能数量会非常巨大，如果粒子纹理是比较复杂的图像模型，一般还会事先通过<strong>纹理贴图（Texture Mapping）</strong>，把<strong>复杂的形状包裹在简单的基础图形中</strong>，来提高渲染性能。</p>
<h4 id="游戏引擎中的粒子系统使用" tabindex="-1">游戏引擎中的粒子系统使用 <a class="header-anchor" href="#游戏引擎中的粒子系统使用" aria-label="Permalink to &quot;游戏引擎中的粒子系统使用&quot;">&ZeroWidthSpace;</a></h4>
<p>大多数游戏引擎可以<strong>可视化编辑</strong>或<strong>配置代码</strong>来生成粒子特效，这里以两个非常有代表性的游戏引擎举例，<strong>Cocos2d-x和Unity3D</strong>是在游戏引擎市场份额非常高的两家了，下面分别试验一下。</p>
<h4 id="cocos2d-x-引擎" tabindex="-1">Cocos2d-x 引擎 <a class="header-anchor" href="#cocos2d-x-引擎" aria-label="Permalink to &quot;Cocos2d-x 引擎&quot;">&ZeroWidthSpace;</a></h4>
<p>14年那个时候还没有Cocos Creator，基于Canvas WebGL等技术的H5游戏还没有兴起，Cocos2d-x只支持C++和Lua两个语言。</p>
<p>所以以前用C++做，需要先用Particle Designer生成一个XML格式的plist文件，然后调用C++的API读取属性，加入Scene中。<strong>Particle Designer</strong>这个软件挺酷炫的（Mac版本，Windows有个简易版的同名软件），下面是链接：<br>
<a href="https://www.71squared.com/particledesigner" target="_blank" rel="noreferrer">https://www.71squared.com/particledesigner</a></p>
<p>时隔几年，现在有了Cocos Creator，C++似乎失宠了。现在<strong>JavaScript/Typescript可以轻松开发Cocos2d-x游戏</strong>，在编译过程中看到了不少带&quot;JSB&quot;的文件，看来吸收了React Native的精髓啊，JavaScript与Native桥接，利用Web技术<strong>一桶浆糊</strong>的能力实现跨平台。</p>
<p>在Cocos Creator中制作粒子特效非常简单，编辑器点点就可以了生成资源文件了（.fire后缀的JSON文件）</p>
<p><img src="//filecdn.code2life.top/cocos_particle.jpg" alt=""></p>
<p><img src="//filecdn.code2life.top/cocos-particle2.jpg" alt=""></p>
<p>至于事件和控制，通过UI配置加上JS/TS调用一下API即可。下面这个代码是不是很有Angular/Vue/React的既视感？其实<strong>MVVM模式和组件化开发</strong>在客户端开发/游戏开发领域早已约定俗成，只是近些年Web前端越来越复杂，大前端的触角伸向越来越多的领域，催生了这些优秀的前端技术和框架。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">cc.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    extends: cc.Component,</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    properties: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        particle: cc.Node,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    resetParticle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> myParticle </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.particle.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(cc.ParticleSystem);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        myParticle.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resetSystem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><h4 id="unity3d-引擎" tabindex="-1">Unity3D 引擎 <a class="header-anchor" href="#unity3d-引擎" aria-label="Permalink to &quot;Unity3D 引擎&quot;">&ZeroWidthSpace;</a></h4>
<p>Unity 3D 更为复杂，需要更多的专业领域知识才能玩的转，笔者并不熟悉，只是简单试验了一下。Unity提供的Particle Editor非常强大，速度，旋转，色彩，3D图形等等每一项都有非常完善的配置，能够自定义调整变化曲线，通过C#脚本可对事件编程。下图只是一个最简单的粒子系统配置的冰山一角。</p>
<p><img src="//filecdn.code2life.top/unity3d_particle.gif" alt=""></p>
<h2 id="javascript库以及web端的使用" tabindex="-1">JavaScript库以及Web端的使用 <a class="header-anchor" href="#javascript库以及web端的使用" aria-label="Permalink to &quot;JavaScript库以及Web端的使用&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>做前端页面</strong>比游戏开发相对简单，整粒子系统就上Canvas，要3D的就上WebGL，基于<strong>现成的轮子做一些简单的五毛特效</strong>比较轻松。在Github上找到一个Star比较多质量很不错的轮子：</p>
<ul>
<li>Proton <a href="https://github.com/a-jie/Proton" target="_blank" rel="noreferrer">https://github.com/a-jie/Proton</a></li>
</ul>
<p>下面我们来分析一下Proton的源码：</p>
<ul>
<li><strong>index.js</strong> 导出所有对外提供的API，Proton类，以及Proton对象下挂了：<strong>粒子初始化属性，粒子行为，发射器，边界区域，渲染方式等等</strong></li>
<li><strong>Proton.js</strong> 有一些<strong>全局方法</strong>，比如获取所有的粒子，更新函数，发射器和渲染方式的管理等等</li>
<li><strong>/emitter</strong> 是发射器的实现，两个比较常用的，一个是默认的静态发射器，还有一个继承Emitter的FollowEmitter，跟踪鼠标移动的发射器，Emitter可以<strong>管理粒子初始化参数，以及预设行为</strong></li>
<li><strong>/initialize</strong> 包括了发射器以及单个粒子的初始化参数类：<strong>质量 Mass，生存周期 Life，半径 Radius，速度 Velocity，位置 Position，粒子的图片 Body，发射速率 Rate</strong></li>
<li><strong>/behaviour</strong> 主要是单个粒子生命周期内的行为，<strong>碰撞，吸引，排斥，旋转，缩放，抖动，重力场，触及边界，外力等等</strong>继承Behaviour类的实现，可以叠加，可以自定义行为</li>
<li><strong>/render</strong> 是各种渲染方式的实现，比如DOM，Canvas，WebGL，EaselJS等等，其中常用的，性能不错的是<strong>Canvas和WebGL</strong></li>
<li><strong>/core</strong> 定义了3个核心类，一个是刚提到的<strong>Proton</strong>，另一个是<strong>Particle</strong>抽象出单个粒子的属性和行为，还有一个<strong>Pool</strong>作为Particle对象的缓存池，管理对象的生成和销毁</li>
</ul>
<p><strong>源码非常清晰，代码质量很不错</strong>，还有很多酷炫的Example让人嗔目咋舌。拿Example中的烟花举例，用Proton写一个粒子效果大概是这样的（截取了部分代码）：</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> canvas;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> context;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> proton;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> renderer;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> emitter;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createProton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Emitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.rate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Rate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Mass</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Radius</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // P = Particle</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">P</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LineZone</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, canvas.height, canvas.width </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, canvas.height)));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Life</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1.5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // V = Velocity </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">V</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">6</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'polar'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Gravity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Color</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'#ff0000'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'random'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 发射烟花，第一级升空</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  emitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">emit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addEmitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(emitter);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  renderer </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CanvasRenderer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(canvas);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  renderer.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">onProtonUpdate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    context.fillStyle </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "rgba(0, 0, 0, 0.1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    context.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fillRect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, canvas.width, canvas.height);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addRenderer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(renderer);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Proton.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">PARTICLE_DEAD</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">particle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    createSubEmitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(particle);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 第二级发射器，烟花升空之后爆炸的特效</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createSubEmitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">particle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> subemitter </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Emitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.rate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Rate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">250</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">300</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Mass</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Radius</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Life</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addInitialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">V</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Span</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">360</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'polar'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RandomDrift</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.05</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Alpha</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Gravity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> color </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> .3</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.MathUtils.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">randomColor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'random'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addBehaviour</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Color</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(color));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.p.x </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> particle.p.x;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.p.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> particle.p.y;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  subemitter.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">emit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'once'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addEmitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(subemitter);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// RAF递归跑起来，每帧刷新</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> tick</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  requestAnimationFrame</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tick);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proton.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})();</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br></div></div><p><img src="//filecdn.code2life.top/firework_particle.gif" alt=""></p>
<h2 id="彩蛋-给博客添加五毛钱的粒子特效" tabindex="-1">彩蛋：给博客添加五毛钱的粒子特效 <a class="header-anchor" href="#彩蛋-给博客添加五毛钱的粒子特效" aria-label="Permalink to &quot;彩蛋：给博客添加五毛钱的粒子特效&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="彩蛋-1-proton-3d" tabindex="-1">彩蛋#1 Proton 3D <a class="header-anchor" href="#彩蛋-1-proton-3d" aria-label="Permalink to &quot;彩蛋#1 Proton 3D&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>Proton.js</strong> 除了2D的实现，作者还有一个基于<strong>three.js</strong> (<a href="https://github.com/mrdoob/three.js/" target="_blank" rel="noreferrer">https://github.com/mrdoob/three.js/</a>) 的3D粒子系统框架实现：<strong>Proton 3D</strong>：<a href="https://github.com/a-jie/three.proton" target="_blank" rel="noreferrer">https://github.com/a-jie/three.proton</a>，设计思路与Proton几乎完全一致，只是Render层使用Three.js和WebGL实现，很多参数从二维变成三维，使用的时候需要先用Three.js&quot;布置&quot;<strong>场景、光源、相机</strong>，相对复杂一些。</p>
<p>感谢这位大佬的开源项目，笔者用这个库装饰了一下个人主页，链接如下，不过大量3D粒子的CPU/GPU消耗不容小觑，可能某些机器或浏览器会稍有卡顿。</p>
<p><a href="//code2life.top/about.html" target="_blank" rel="noreferrer">//code2life.top/about.html</a></p>
<h4 id="彩蛋-2-gravity-points" tabindex="-1">彩蛋#2 Gravity Points <a class="header-anchor" href="#彩蛋-2-gravity-points" aria-label="Permalink to &quot;彩蛋#2 Gravity Points&quot;">&ZeroWidthSpace;</a></h4>
<p>另外，之前在CodePen上偶然看到了一个非常有趣的粒子特效 GravityPoints，于是稍微改了一下放到笔者自己的博客背景上了，为了避免影响窄屏的阅读体验，目前<strong>只在在宽屏浏览器上显示粒子特效</strong>。如下图：</p>
<p><img src="//filecdn.code2life.top/gravity.gif" alt=""></p>
<p>非常感谢和佩服那位作者的创意，这个效果是直接调用Canvas的API画出来的，加在一起只有几百行代码。其实粒子系统并不复杂，每一种粒子行为都很简单，但叠加在一起就能迸发神奇的魔力，这也是<strong>软件工程最重要的思想之一 ———— 分而治之的完美体现</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[ElasticSearch索引生命周期管理]]></title>
            <link>https://code2life.top/blog/0044-es-ilm-introduction</link>
            <guid>https://code2life.top/blog/0044-es-ilm-introduction</guid>
            <pubDate>Wed, 05 Jun 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="elasticsearch索引生命周期管理" tabindex="-1">ElasticSearch索引生命周期管理 <a class="header-anchor" href="#elasticsearch索引生命周期管理" aria-label="Permalink to &quot;ElasticSearch索引生命周期管理&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="缘起" tabindex="-1">缘起 <a class="header-anchor" href="#缘起" aria-label="Permalink to &quot;缘起&quot;">&ZeroWidthSpace;</a></h2>
<p>最近笔者在开发分布式项目过程中遇到了小问题，<strong>本地调试的时候搜索日志很不方便</strong>，因为一个HTTP请求经过多个组件，需要在IDEA来回切多个项目，而且本地重启之后IDEA控制台的日志也丢失了，想对比上次运行的结果只能搜索日志文件。那么问题来了，<strong>本地开发是否也可以把日志集中起来，最好还可以全文搜索呢</strong>？</p>
<p>其实测试环境，预上线环境和产线环境，都有一套日志系统，所有组件的日志通过各种途径上传到Kafka最终写入ElasticSearch中，但这套复杂的日志系统是为了应对产线极其恐怖的日志量，在本地部署一整套比较困难，于是笔者想到了一个简单的办法来索引本地日志，Filebeat直接输出到ES，然后在Kibana中搜索和可视化，也就是极简版EFK栈。</p>
<p>其实本地Filebeat直接输出到ElasticSearch的配置是很简单的，只需要三步：</p>
<ol>
<li>配置input</li>
<li>配置output</li>
<li>启动filebeat</li>
</ol>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Step1. input 的配置，告诉crawler抓取哪些文件的变化</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">filebeat.inputs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">log</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  enabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  paths</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">d:\logs\*</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  fields</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 自定义传给ES的JSON属性，用来区分不同的组件</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'my-project'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  fields_under_root</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 每条日志以时间戳开头，用来匹配多行日志，</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 防止一个Stacktrace被分成多个Document</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  multiline</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">     pattern</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">^\d{4}-\d{1,2}-\d{1,2}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">     negate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">     match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">after</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Step2. output和template配置</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">setup.template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"log"</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 这个pattern要与output的index配置一致</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  pattern</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"log-xxx*"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  settings</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 本地日志不需要多副本和分片，这样节省一点ES的资源</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    index.number_of_shards</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    index.number_of_replicas</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># ES的地址和访问方式</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">output.elasticsearch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"es-host:9200"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  username</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"xxx"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  password</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"xxx"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  index</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"log-xxx-%{+yyyy.MM.dd}"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  ssl.verification_mode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">none</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>但是，启动filebeat之后，神奇的事情发生了，<strong>在ES中生成的索引，并不是我指定的log-xxx,而是filebeat-xxx</strong>，明明配置文件指定的是log-xxx啊！
作为一个喜欢刨(zuan)根(niu)问(jiao)底(jian)的工程狮，无法忍受这个与预期行为不一样的事情发生，于是顺着问题查下去，结果发现了一个新大陆！
这就是今天要介绍的主角<strong>Index Lifecycle Management(ILM)</strong></p>
<h2 id="笃行" tabindex="-1">笃行 <a class="header-anchor" href="#笃行" aria-label="Permalink to &quot;笃行&quot;">&ZeroWidthSpace;</a></h2>
<p>在学习实践ILM之前，首先解释一下为什么Filebeat配置的Index Pattern设置失效了。Filebeat下载的是当前最新版本7.1.1，而恰巧开发环境的ElasticSearch最近重新搭建的，也升级到了7.1.0版本。<strong>而ElasticStack从2019年1月29日的6.6.0版本的开始，引入了索引生命周期管理的功能，新版本的Filebeat则默认的配置开启了ILM，导致索引的命名规则被ILM策略控制</strong>。</p>
<p>详见Release Notes：
<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.6/release-notes-6.6.0.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/reference/6.6/release-notes-6.6.0.html</a></p>
<p>这里有个历史背景，在2018年3月份的时候，Elastic官方宣布开源包含各种ES高级功能的X-Pack，链接参考这里<a href="https://www.elastic.co/products/x-pack/open" target="_blank" rel="noreferrer">https://www.elastic.co/products/x-pack/open</a>，开源X-Pack之后也提供了Apache-2.0 License的Open Source Software (OSS) 版本，而带有X-Pack的新版本则用<a href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt" target="_blank" rel="noreferrer">ELASTIC-LICENSE</a>免费提供，不用升级License也可以享用X-Pack的高级功能。
<img src="//filecdn.code2life.top/elastic_license.jpg" alt=""></p>
<p>找到根本原因之后，先解决掉上面的问题，然后开始实践一下ES的索引生命周期管理这个新功能。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># filebeat 配置关闭 ILM 即可解决Index Pattern不生效的问题</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">setup.ilm.enabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如果要开启ILM的话，这里是Filebeat ILM相关配置的文档</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># https://www.elastic.co/guide/en/beats/filebeat/current/ilm.html</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h4 id="细说索引生命周期管理" tabindex="-1">细说索引生命周期管理 <a class="header-anchor" href="#细说索引生命周期管理" aria-label="Permalink to &quot;细说索引生命周期管理&quot;">&ZeroWidthSpace;</a></h4>
<p>ElasticSearch作为一个全文搜索引擎，索引即是灵魂。在海量数据的场景下，管理索引是非常有挑战的事情：</p>
<ul>
<li>增长过快的索引通常需要切分成多个提高搜索效率；</li>
<li>时效性强的数据，比如日志和监控数据，需要定期清理或者归档；</li>
<li>旧数据通过索引压缩，减少分片节约计算资源；</li>
<li>数据冷热分离到SSD和HDD硬盘节约存储成本等等</li>
</ul>
<p>曾经这些事情需要专业的ES运维，人工处理或者借助Curator这样的开源工具来操作。而现在新版本的ES自带的索引生命周期管理（ILM），是解决这些问题的最佳实践的官方标准。<strong>ILM把索引分成4个阶段，每个阶段能够调整索引的优先级，在达到临界条件后执行相应的Action</strong>：</p>
<ul>
<li><strong>Hot阶段</strong> 热数据，索引优先级可以比较高，增长过快可以<strong>Rollover</strong></li>
<li><strong>Warm阶段</strong> 还有余热的数据，可以合并索引，<strong>减少Shard分片（Shrink）</strong>，设置只读等等，</li>
<li><strong>Cold阶段</strong> 冷数据，食之无味弃之可惜，这些数据可能已经很久都不会查询到一次，但是可能哪天还会用到，可以当做归档数据<strong>Freeze</strong>掉了，</li>
<li><strong>Delete阶段</strong> 这些数据可能一辈子都不会查询到了，那就快刀斩乱麻，给新数据腾点存储空间吧</li>
</ul>
<p>这四个阶段，每个阶段都有独特的Action，也有多个阶段都可以使用的Action，比如<strong>Allocate</strong>就可以在Warm和Cold两个阶段使用，用来<strong>灵活的改变分片Shard数量，副本Replicas数量以及存储节点</strong>，比如这个ILM Policy就可以实现数据的冷热分离：</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span># 存储两个副本, 并且转移索引数据到冷数据节点</span></span>
<span class="line"><span>PUT _ilm/policy/log_data_policy</span></span>
<span class="line"><span>{</span></span>
<span class="line"><span>  "policy": {</span></span>
<span class="line"><span>    "phases": {</span></span>
<span class="line"><span>      "warm": {</span></span>
<span class="line"><span>        "actions": {</span></span>
<span class="line"><span>          "allocate" : {</span></span>
<span class="line"><span>            "number_of_replicas": 2,</span></span>
<span class="line"><span>            "require" : {</span></span>
<span class="line"><span>              "box_type": "cold"</span></span>
<span class="line"><span>            }</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>      }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>为什么这个ILM策略可以实现冷热分离呢? 我们知道ES集群<strong>Master节点用来协调调度, Client节点用来处理API请求, Data节点用来索引存储数据</strong>。其中<strong>Data节点</strong>可以配置 <strong>node.attr.box_type</strong>，比如<strong>SSD节点设置这个值为hot，HDD节点设置这个值为cold</strong>，那么对于达到Warm条件的索引数据, 就会转移到HDD节点了，也就实现了冷热分离。关于不同阶段不同Action的策略配置，详见下面的文档链接：</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/_actions.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/reference/current/_actions.html</a></p>
<h4 id="ilm的简单实践" tabindex="-1">ILM的简单实践 <a class="header-anchor" href="#ilm的简单实践" aria-label="Permalink to &quot;ILM的简单实践&quot;">&ZeroWidthSpace;</a></h4>
<p>在学习应用ElasticStack时，看官方文档是个很棒的办法，文档非常详细，此处Mark一下传送门：</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html</a></p>
<p>以及ES索引生命周期管理的文档传送门：</p>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html</a></p>
<p>我们可以一边参考文档, 一边在<strong>Kibana的Dev Tools</strong>里面编写API请求来试验, Dev Tools堪称神器, 光标移到每一个请求上<strong>Ctrl + Enter</strong>立刻见效:</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 常用的 _cluster API 6连</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/health?pretty</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/health?pretty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x26;level</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">indices</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/health?pretty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x26;level</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">indices</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x26;level</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">shards</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/settings</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/state</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cluster/stats?human</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pretty</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 常用的 _cat API 3连</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cat/nodes?v</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cat/templates?v</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _cat/indices?v</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 常用的 _node API 3连</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _nodes?pretty</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _nodes/stats</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GET</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> _nodes/usage</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>刀磨好了，开始砍柴。给索引添加ILM策略有两个方式，一是调用直接Create/Update Index的API设置<strong>单个索引</strong>的生命周期管理策略，二是通过<strong>Index Template</strong>关联ILM策略，这样可以一劳永逸，所有同一个Template的Indices都能被ILM控制。Filebeat MetricBeat等全家桶组件也都是用这种方式的。我们来尝试把现有的一个Index Template设置ILM。</p>
<h5 id="第一步-创建ilm策略-比如这样的策略-每天或者达到50gb轮滚一次-30天后缩成1个分片-合并索引-并且增加副本-60天后转移到冷数据节点-90天后删除" tabindex="-1">第一步, 创建ILM策略, 比如这样的策略: <strong>每天或者达到50GB轮滚一次, 30天后缩成1个分片,合并索引,并且增加副本, 60天后转移到冷数据节点, 90天后删除</strong>: <a class="header-anchor" href="#第一步-创建ilm策略-比如这样的策略-每天或者达到50gb轮滚一次-30天后缩成1个分片-合并索引-并且增加副本-60天后转移到冷数据节点-90天后删除" aria-label="Permalink to &quot;第一步, 创建ILM策略, 比如这样的策略: **每天或者达到50GB轮滚一次, 30天后缩成1个分片,合并索引,并且增加副本, 60天后转移到冷数据节点, 90天后删除**:&quot;">&ZeroWidthSpace;</a></h5>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>PUT _ilm/policy/log_policy</span></span>
<span class="line"><span>{</span></span>
<span class="line"><span>  "policy": {</span></span>
<span class="line"><span>    "phases": {</span></span>
<span class="line"><span>      "hot": {</span></span>
<span class="line"><span>        "actions": {</span></span>
<span class="line"><span>          "rollover": {</span></span>
<span class="line"><span>            "max_age": "1d",</span></span>
<span class="line"><span>            "max_size": "50G"</span></span>
<span class="line"><span>          }</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>      },</span></span>
<span class="line"><span>      "warm": {</span></span>
<span class="line"><span>        "min_age": "30d",</span></span>
<span class="line"><span>        "actions": {</span></span>
<span class="line"><span>          "forcemerge": {</span></span>
<span class="line"><span>            "max_num_segments": 1</span></span>
<span class="line"><span>          },</span></span>
<span class="line"><span>          "shrink": {</span></span>
<span class="line"><span>            "number_of_shards": 1</span></span>
<span class="line"><span>          },</span></span>
<span class="line"><span>          "allocate": {</span></span>
<span class="line"><span>            "number_of_replicas": 2</span></span>
<span class="line"><span>          }</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>      },</span></span>
<span class="line"><span>      "cold": {</span></span>
<span class="line"><span>        "min_age": "60d",</span></span>
<span class="line"><span>        "actions": {</span></span>
<span class="line"><span>          "allocate": {</span></span>
<span class="line"><span>            "require": {</span></span>
<span class="line"><span>              "box_type": "cold"</span></span>
<span class="line"><span>            }</span></span>
<span class="line"><span>          }</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>      },</span></span>
<span class="line"><span>      "delete": {</span></span>
<span class="line"><span>        "min_age": "90d",</span></span>
<span class="line"><span>        "actions": {</span></span>
<span class="line"><span>          "delete": {}</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>      }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br></div></div><h5 id="第二步-找到template-通过-get-template-log-xxx-查看一下setting-确认没有-settings-index-lifecycle-name-rollover-alias-这两个属性" tabindex="-1">第二步 找到Template, 通过 <strong>GET _template/log-xxx</strong> 查看一下setting, 确认没有 settings.index.lifecycle.name/rollover_alias 这两个属性 <a class="header-anchor" href="#第二步-找到template-通过-get-template-log-xxx-查看一下setting-确认没有-settings-index-lifecycle-name-rollover-alias-这两个属性" aria-label="Permalink to &quot;第二步 找到Template, 通过 **GET _template/log-xxx** 查看一下setting, 确认没有 settings.index.lifecycle.name/rollover_alias 这两个属性&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="第三步-更新这个index-template-应用第一步创建的-log-policy" tabindex="-1">第三步 更新这个Index Template, 应用第一步创建的 &quot;log_policy&quot; <a class="header-anchor" href="#第三步-更新这个index-template-应用第一步创建的-log-policy" aria-label="Permalink to &quot;第三步 更新这个Index Template, 应用第一步创建的 &quot;log_policy&quot;&quot;">&ZeroWidthSpace;</a></h5>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>PUT _template/log-xxx</span></span>
<span class="line"><span>{</span></span>
<span class="line"><span>  "index_patterns": ["log-xxx-*"], </span></span>
<span class="line"><span>  "settings": {</span></span>
<span class="line"><span>    "number_of_shards": 1,</span></span>
<span class="line"><span>    "number_of_replicas": 1,</span></span>
<span class="line"><span>    "index.lifecycle.name": "log_policy", </span></span>
<span class="line"><span>    "index.lifecycle.rollover_alias": "log-xxx"</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><h5 id="第四步-查看policy以及template创建出来的新的索引策略是否应用成功" tabindex="-1">第四步 查看Policy以及Template创建出来的新的索引策略是否应用成功 <a class="header-anchor" href="#第四步-查看policy以及template创建出来的新的索引策略是否应用成功" aria-label="Permalink to &quot;第四步 查看Policy以及Template创建出来的新的索引策略是否应用成功&quot;">&ZeroWidthSpace;</a></h5>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>GET _ilm/policy</span></span>
<span class="line"><span></span></span>
<span class="line"><span>GET log-xxx-*/_ilm/explain</span></span>
<span class="line"><span># managed 为 true 即已经被ILM管理</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>只要几个API就能完成这么复杂的索引生命周期管理!</p>
<h4 id="与curator对比" tabindex="-1">与Curator对比 <a class="header-anchor" href="#与curator对比" aria-label="Permalink to &quot;与Curator对比&quot;">&ZeroWidthSpace;</a></h4>
<p>Curator是一个Python实现的开源的ES索引管理项目: <a href="https://github.com/elastic/curator" target="_blank" rel="noreferrer">https://github.com/elastic/curator</a></p>
<p>在ILM出现之前，Curator可以说是一个ElasticSearch索引管理的必备品，定时创建新的索引，删除、合并、压缩、备份/恢复旧的索引，通过简单的<strong>YAML配置 + Crontab</strong>即可实现。那么有了ILM之后，Curator的定位就比较尴尬了，Curator能做的ILM都能做，而且还更简单。官方文档也给出了最佳实践，翻译概括一下就是：咱的Beats系列和Logstash都能利用ILM的特性，还不赶紧丢掉Curator来用ILM？
<a href="https://www.elastic.co/guide/en/elasticsearch/client/curator/5.7/ilm-or-curator.html" target="_blank" rel="noreferrer">https://www.elastic.co/guide/en/elasticsearch/client/curator/5.7/ilm-or-curator.html</a></p>
<h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>目前业界常见的日志收集技术栈大多是通过不同的媒介，最终<strong>汇集到ElasticSearch中</strong>，大型应用通常会用Kafka作为Broker削峰填谷，防止ES被压垮。这种架构可以也把日志的<strong>处理分析相关组件</strong>如Logstash、流式计算和大数据分析等与日志收集的Fluentd，Fluent bit，Filebeat这些<strong>收集组件解耦</strong>，构建企业级日志系统。</p>
<p>本篇开头配置的最简版EFK栈，通过配置参数的调优，已经足够很大量级的日志写入和索引了。记得曾经看过一篇文章，那篇文章的作者只用Filebeat + ElasticSearch 就实现了<strong>4000 QPS的吞吐</strong>，大约相当于每天TB级的日志数据。而且Filebeat对数据的预处理能力也非常强大，效率比Logstash还高，因此这种<strong>纯EFK的日志架构</strong>未偿不是一个简单可靠的方案。</p>
<p>另外Filebeat原生支持容器日志收集，配置非常简洁，<strong>寥寥几行就可以网罗整个容器集群的日志</strong>，而如今已经非常成熟的Kubernetes、容器化技术对日志方面带来了更大的便利：<strong>业务应用再也无需考虑日志轮滚，日志归档，文件命名</strong>等等琐事，<strong>一股脑输出到标准输出，标准错误即可</strong>，剩下的交给<strong>云原生基础设施</strong>吧。</p>
<p>总的来说，ElasticSearch如今生态已经逐渐丰富起来，发展成了<strong>ElasticStack全家桶</strong>，各大云服务商也都提供开箱即用的ElasticStack云服务，而同样基于Apache Lucene引擎的<strong>Solr</strong>在全文搜索引擎的市场已经无法与ES匹敌，ES不仅是一个全文搜索引擎，其使用场景也非常广泛：日志，监控，大数据分析，数据可视化，机器学习等等。了解ES对于任何一个从业者都是非常有价值的，不仅是使用方法，了解其设计思路和实现原理也大有裨益。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[使用JMH编写Java基准测试]]></title>
            <link>https://code2life.top/blog/0043-jmh-benchmark</link>
            <guid>https://code2life.top/blog/0043-jmh-benchmark</guid>
            <pubDate>Sat, 11 May 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="使用jmh编写java基准测试" tabindex="-1">使用JMH编写Java基准测试 <a class="header-anchor" href="#使用jmh编写java基准测试" aria-label="Permalink to &quot;使用JMH编写Java基准测试&quot;">&ZeroWidthSpace;</a></h1>
<p>最近因为项目需要重拾Java，项目涉及到很多日期时间相关的计算和存储，所以打算用极其便捷且线程安全的LocalDateTime类型代替古老的Date类型，那么问题来了：<strong>Date类型和LocalDateTime类型的JSON解析的性能如何呢</strong>？</p>
<h2 id="openjdk-jmh" tabindex="-1">OpenJDK JMH <a class="header-anchor" href="#openjdk-jmh" aria-label="Permalink to &quot;OpenJDK JMH&quot;">&ZeroWidthSpace;</a></h2>
<p>在准备开始做性能基准测试之前，突然想到Golang开箱即用的精确到纳秒级别的Benchmark功能，Java有没有类似的工具或者类库呢？于是搜索一下，找到了JMH。</p>
<blockquote>
<p>JMH，即Java Microbenchmark Harness，是专门用于代码微基准测试的工具套件。何谓Micro Benchmark呢？简单的来说就是基于方法层面的基准测试，精度可以达到微秒级。当你定位到热点方法，希望进一步优化方法性能的时候，就可以使用JMH对优化的结果进行量化的分析。</p>
</blockquote>
<p>JMH是OpenJDK开发的，然而OpenJDK并没有自带该类库，Oracle的也没有，需要额外引入依赖并配置IDE。详细文档和示例参考文档：  <a href="//openjdk.java.net/projects/code-tools/jmh/" target="_blank" rel="noreferrer">//openjdk.java.net/projects/code-tools/jmh/</a></p>
<h4 id="idea下gradle项目的配置" tabindex="-1">IDEA下Gradle项目的配置 <a class="header-anchor" href="#idea下gradle项目的配置" aria-label="Permalink to &quot;IDEA下Gradle项目的配置&quot;">&ZeroWidthSpace;</a></h4>
<p>Maven与Gradle配置JMH类似，都是添加测试Scope的依赖即可，以Gradle为例：</p>
<div class="language-groovy vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">groovy</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">testCompile(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.openjdk.jmh:jmh-core:1.21"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">testCompile(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"org.openjdk.jmh:jmh-generator-annprocess:1.21"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>然后IDEA安装JMH插件方便生成和运行JMH测试用例，在IDEA里面直接搜索安装即可（下图不小心暴露了一些其他非常好用的IDEA插件）
<img src="//filecdn.code2life.top/idea-plugins.png" alt=""></p>
<p>插件装好并重启IDEA后，开启IDEA的Annotation Processing功能，就可以在项目的test目录下新建一些Benchmark测试代码了
<img src="//filecdn.code2life.top/annotation-processor.png" alt=""></p>
<p>注：笔者还遇到了这个报错，mvn/gradle clean一下，再刷新一下依赖，重启IDEA解决</p>
<blockquote>
<p>Can not found /META-INF/BenchmarkList (JMH Unable to find the resource: /META-INF/BenchmarkList)</p>
</blockquote>
<h2 id="benchmark测试用例的编写和执行" tabindex="-1">Benchmark测试用例的编写和执行 <a class="header-anchor" href="#benchmark测试用例的编写和执行" aria-label="Permalink to &quot;Benchmark测试用例的编写和执行&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="编写方法和常用注解" tabindex="-1">编写方法和常用注解 <a class="header-anchor" href="#编写方法和常用注解" aria-label="Permalink to &quot;编写方法和常用注解&quot;">&ZeroWidthSpace;</a></h4>
<p>在开始编码之前，我们需要了解一下JMH的常用注解的含义，使用这些注解和在main函数中创建org.openjdk.jmh.runner.options.OptionsBuilder，通过链式调用构建Options等价，也和直接通过java -jar benchmark.jar 添加参数等价，但这里只讲解<strong>通过注解来配置测试参数</strong>，不因为别的，就因为注解更酷炫。</p>
<ul>
<li><strong>@Benchmark</strong>：每个带Benchmark注解的都是一个微基准测试用例</li>
<li><strong>@BenchmarkMode</strong>：吞吐量或者平均时间等等，配合@OutputTimeUnit作为<strong>计量指标</strong></li>
<li><strong>@Fork</strong>：每个测试Fork进程执行，指定Fork的数量，这是为了防止JVM <strong>PGO</strong>（Profile-Guided Optimization）影响测试结果</li>
<li><strong>@Threads</strong>：每个基准测试启动的<strong>并发</strong>线程数量</li>
<li><strong>@State</strong>：State有3个Scope，分别是Benchmark，Group，Thread
<ul>
<li><strong>Thread</strong>表示变量只在同一个线程同步，一般用Thread，用到的成员变量相当于是ThreadLocal的</li>
<li><strong>Benchmark</strong>表示变量整个测试的进程同步，比如方法A用到了变量B，线程C和线程D访问B是同一个变量需要加锁，而不是ThreadLocal的</li>
<li><strong>Group</strong>则是同一组的测试使用同样的变量，配合@Group @GroupThreads 给关联的测试分组和同步使用</li>
</ul>
</li>
<li><strong>@Warmup @Measurement</strong>：预热和正式开始的轮数，每次迭代时间和循环次数都在这里配置</li>
<li><strong>@Setup @TearDown</strong>：这俩相当与JUnit4的@Before @After，做一些初始化和资源回收的事情，有3个Level
<ul>
<li><strong>Trial</strong> 是默认值相当于 @BeforeClass @AfterClass</li>
<li><strong>Iteration</strong> 表示每一轮预热或者正式测试都执行一次</li>
<li><strong>Invocation</strong> 相当于 @Before @After,每次调用@Benchmark的函数都会执行一次，这个一般不要用，会干扰测试结果</li>
</ul>
</li>
<li><strong>@Param</strong>：轮换指定不同的调用参数测试</li>
</ul>
<p>除了这些还有一些不常用的注解此处不展开解释，除了注解以外，还有一个非常重要的JMH的类：<strong>Blackhole</strong>，在编写基准测试的时候，很容易上<strong>编译器或者JIT</strong>的当，造成不准确的结果</p>
<ul>
<li>某些数值计算在<strong>编译阶段就被算成常数</strong>了</li>
<li><strong>循环展开机制</strong>，编译器可能把循环300次自动优化成循环100次，每次循环递增调用3次等等</li>
<li>一些<strong>没有副作用，返回值又没有赋值</strong>的函数，编译器或者JIT可能直接会整个函数调用都给直接省掉</li>
</ul>
<p>因此，在用JMH写Java基准测试的时候，尽量<strong>不要自己写for循环</strong>，通过<strong>注解或者构建参数告诉Runner循环迭代的配置</strong>即可，@Benchmark函数<strong>调用的结果，最好是return掉，或者用Blackhole消费掉</strong>，Blackhole的consume方法就是接收函数返回值，防止调用被JIT优化掉的。</p>
<h4 id="代码实例" tabindex="-1">代码实例 <a class="header-anchor" href="#代码实例" aria-label="Permalink to &quot;代码实例&quot;">&ZeroWidthSpace;</a></h4>
<p>了解了JMH的概念和使用之后，开始撸代码，这里写了两个@Benchmark，分别是反序列化同一个常量JSON字符串，到两个不同的Class，一个用<strong>LocalDateTime</strong>接收时间戳，另一个用<strong>Date</strong>接收，用的是业界最成熟的JSON库<strong>Jackson</strong>。</p>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> code2life.benchmark;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> com.fasterxml.jackson.databind.JavaType;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> com.fasterxml.jackson.databind.ObjectMapper;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> com.fasterxml.jackson.databind.SerializationFeature;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lombok.Data;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.annotations.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.infra.Blackhole;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.runner.Runner;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.runner.RunnerException;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.runner.options.Options;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> org.openjdk.jmh.runner.options.OptionsBuilder;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.io.IOException;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.time.LocalDateTime;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.util.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> java.util.concurrent.TimeUnit;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">BenchmarkMode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({Mode.Throughput})</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">OutputTimeUnit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(TimeUnit.SECONDS)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Warmup</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">iterations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Measurement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">iterations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Threads</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">State</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Scope.Thread)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Jackson2UtilBenchmark</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String raw </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "[{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:1,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">a</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">],"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            "</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">createTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-09T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">modifyTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-10T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">},"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            "{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:2,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">e</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">f</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">],</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">createTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-01T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">            "</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">modifyTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-02T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}]"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ObjectMapper objectMapper </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ObjectMapper</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> JavaType javaType </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getTypeFactory</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">constructParametricType</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ArrayList.class, SimplePojo.class);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> JavaType javaTypeDate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getTypeFactory</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">constructParametricType</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ArrayList.class, SimplePojoDate.class);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Setup</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Level.Trial)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setup</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">findAndRegisterModules</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">disable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Benchmark</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> parseLocalDateTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Blackhole </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">bh</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">throws</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> IOException {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        bh.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">consume</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">readValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(raw, javaType));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    @</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Benchmark</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> parseDate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Blackhole </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">bh</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">throws</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> IOException {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        bh.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">consume</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(objectMapper.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">readValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(raw, javaTypeDate));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">throws</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> RunnerException {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Options options </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> OptionsBuilder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">include</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Jackson2UtilBenchmark.class.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getSimpleName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Runner</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(options).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Data</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SimplePojo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Integer id;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String name;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> List&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> data;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> LocalDateTime createTime;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> LocalDateTime modifyTime;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">@</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Data</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SimplePojoDate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Integer id;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> String name;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> List&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> data;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Date createTime;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Date modifyTime;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br></div></div><h4 id="运行结果" tabindex="-1">运行结果 <a class="header-anchor" href="#运行结果" aria-label="Permalink to &quot;运行结果&quot;">&ZeroWidthSpace;</a></h4>
<p>下面是笔者本机<strong>单线程反序列化常量JSON数据</strong>的基准性能测试结果</p>
<ul>
<li>运行环境： Intel i7-8550U CPU @ 1.80GHz</li>
<li>操作系统： Ubuntu 18.04 64 Bit</li>
<li>JRE： HotSpot 64-Bit JVM (1.8.0 191-b12)</li>
</ul>
<p>示例代码运行结果：</p>
<table tabindex="0">
<thead>
<tr>
<th>Benchmark</th>
<th style="text-align:left">Mode</th>
<th style="text-align:left">Cnt</th>
<th style="text-align:left">Score</th>
<th style="text-align:left">Error</th>
<th style="text-align:left">Units</th>
</tr>
</thead>
<tbody>
<tr>
<td>parseDate</td>
<td style="text-align:left">thrpt</td>
<td style="text-align:left">2</td>
<td style="text-align:left">351196.715</td>
<td style="text-align:left"></td>
<td style="text-align:left">ops/s</td>
</tr>
<tr>
<td>parseLocalDateTime</td>
<td style="text-align:left">thrpt</td>
<td style="text-align:left">2</td>
<td style="text-align:left">208930.702</td>
<td style="text-align:left"></td>
<td style="text-align:left">ops/s</td>
</tr>
</tbody>
</table>
<p>只有单个Date/LocalDateTime属性的JSON：</p>
<table tabindex="0">
<thead>
<tr>
<th>Benchmark</th>
<th style="text-align:left">Mode</th>
<th style="text-align:left">Cnt</th>
<th style="text-align:left">Score</th>
<th style="text-align:left">Error</th>
<th style="text-align:left">Units</th>
</tr>
</thead>
<tbody>
<tr>
<td>onlyDateField</td>
<td style="text-align:left">thrpt</td>
<td style="text-align:left">2</td>
<td style="text-align:left">1514370.638</td>
<td style="text-align:left"></td>
<td style="text-align:left">ops/s</td>
</tr>
<tr>
<td>onlyLocalDateTime</td>
<td style="text-align:left">thrpt</td>
<td style="text-align:left">2</td>
<td style="text-align:left">925243.048</td>
<td style="text-align:left"></td>
<td style="text-align:left">ops/s</td>
</tr>
</tbody>
</table>
<p>只接收String和Integer类型，不做Date的转换</p>
<table tabindex="0">
<thead>
<tr>
<th>Benchmark</th>
<th style="text-align:left">Mode</th>
<th style="text-align:left">Cnt</th>
<th style="text-align:left">Score</th>
<th style="text-align:left">Error</th>
<th style="text-align:left">Units</th>
</tr>
</thead>
<tbody>
<tr>
<td>parseStringOnly</td>
<td style="text-align:left">thrpt</td>
<td style="text-align:left">2</td>
<td style="text-align:left">1014225.819</td>
<td style="text-align:left"></td>
<td style="text-align:left">ops/s</td>
</tr>
</tbody>
</table>
<p>结果不出意料，LocalDateTime的数据结构更复杂，而Date只有一个毫秒数的时间戳（对应更简单的java.time.Instant类），因此用它接收反序列化的JSON数据效率更低，性能大约<strong>损失了三四成</strong>的样子。对于只有一个时间属性的JSON数据差距更大，但相对于更<strong>耗时的业务来说，性能瓶颈不可能在这里</strong>，这一点性能差异可以忽略不计，因此笔者还是决定继续使用LocalDateTime，摒弃Date。</p>
<p><strong>另外，在Windows 10 i7 7700 3.6GHz上单线程测试结果类似</strong>：</p>
<ul>
<li>对于单个Date/LocalDateTime属性的吞吐量分别是每秒 170万，102万次</li>
<li>包含Integer String List Date等复杂一点的数据，分别是每秒37万，25万次</li>
<li>Thread调成2或4，多线程并行解析会有很大的提高，但低于100%，线程越多上下文切换也越多，收益也越少，物理核数的线程数性能达到最大值</li>
</ul>
<h2 id="其他语言的横向对比" tabindex="-1">其他语言的横向对比 <a class="header-anchor" href="#其他语言的横向对比" aria-label="Permalink to &quot;其他语言的横向对比&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="javascript的json反序列化性能" tabindex="-1">JavaScript的JSON反序列化性能 <a class="header-anchor" href="#javascript的json反序列化性能" aria-label="Permalink to &quot;JavaScript的JSON反序列化性能&quot;">&ZeroWidthSpace;</a></h4>
<p>说完了Java，笔者脑袋一拍，对于<strong>JSON的祖宗JS</strong>，反序列化性能会不会更好呢？我们来比比看。继续上面的例子，<strong>把LocalDateTime改成String，这样数据中只有Integer和String两种类型，在JS中直接调用JSON.parse</strong>。NPM中找到了Benchmark.js这个库，但是为了能与Deno横向对比，没有使用该库，直接用循环计算：</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> json</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "[{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:1,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">a</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">c</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">],"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">createTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-09T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">modifyTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-10T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">},"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:2,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">name2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">data</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">e</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">f</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">],</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">createTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-01T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">,"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">modifyTime</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2019-05-02T01:53:13.396Z</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}]"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// warmup</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  JSON</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(json);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// parse</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> start</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">valueOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  JSON</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(json);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  JSON</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(json);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10000000</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">valueOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> start) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">node</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> json-benchmark.js</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">deno</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> json-benchmark.ts</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>执行结果有些令人吃惊:</p>
<ul>
<li><strong>NodeJS</strong>执行两次： 531985 ops/s, 542387 ops/s</li>
<li><strong>Deno</strong>执行两次：514959 ops/s, 488472 ops/s</li>
</ul>
<p>而Java在同样数据的情况下，两次执行分别是： <strong>987421 ops/s, 1011526 ops/s</strong>。足足比Java差了一倍！</p>
<p>但细细一想确实是符合逻辑的，NodeJS和Deno的JSON.parse函数虽然都是基于V8引擎的Native代码，代码在这里：<a href="https://github.com/v8/v8/blob/master/src/json/json-parser.cc" target="_blank" rel="noreferrer">https://github.com/v8/v8/blob/master/src/json/json-parser.cc</a>.
可见<strong>JSON.parse转换成的是V8的数据类型</strong>，给JS解释器使用的，相对于静态类型的Java直接定义POJO必然需要多做一层处理。NodeJS的Native Code基于C++，而Deno是Rust，源代码或者编译器可能并没有C++成熟，也可以解释<strong>Deno的性能比NodeJS稍差</strong>，但二者同样<strong>基于V8没有本质区别</strong>。</p>
<h4 id="c-的json反序列化性能" tabindex="-1">C#的JSON反序列化性能 <a class="header-anchor" href="#c-的json反序列化性能" aria-label="Permalink to &quot;C#的JSON反序列化性能&quot;">&ZeroWidthSpace;</a></h4>
<p>比完了ECMAScript，一不做二不休，继续来和笔者最喜欢的C#比一比JSON反序列化。测试环境是Ubuntu <strong>Dotnet Core 2.2.204</strong>，Benchmark和JMH异曲同工，只不过Annotation在C#中叫Attribute。</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>dotnet add package Newtonsoft.json</span></span>
<span class="line"><span>dotnet add package BenchmarkDotNet</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><div class="language-csharp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">csharp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Collections</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Generic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Linq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BenchmarkDotNet</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Attributes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BenchmarkDotNet</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Running</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Newtonsoft</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Json</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Newtonsoft</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Json</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Linq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> benchmark</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Program</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> summary</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> BenchmarkRunner.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">JsonBenchmark</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CoreJob</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(baseline</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RPlotExporter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RankColumn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> JsonBenchmark</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> raw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "... json data ..."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Benchmark</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> JsonParseStatic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      JsonConvert.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">DeserializeObject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">List</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SimpleData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>>(raw);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SimpleData</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> List</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> modifyTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br></div></div><blockquote>
<p>dotnet run -c release</p>
</blockquote>
<p>测试结果如下，平均 <strong>4.118 s/op</strong>，吞吐量大约 <strong>242836 ops/s</strong></p>
<table tabindex="0">
<thead>
<tr>
<th>Method</th>
<th style="text-align:right">Mean</th>
<th style="text-align:right">Error</th>
<th style="text-align:right">StdDev</th>
<th style="text-align:right">Ratio</th>
<th style="text-align:right">Rank</th>
</tr>
</thead>
<tbody>
<tr>
<td>JsonParseStatic</td>
<td style="text-align:right">4.118 us</td>
<td style="text-align:right">0.0165 us</td>
<td style="text-align:right">0.0147 us</td>
<td style="text-align:right">1.00</td>
<td style="text-align:right">1</td>
</tr>
</tbody>
</table>
<p><img src="//filecdn.code2life.top/r_result_benchmark.png" alt=""></p>
<h2 id="彩蛋" tabindex="-1">彩蛋 <a class="header-anchor" href="#彩蛋" aria-label="Permalink to &quot;彩蛋&quot;">&ZeroWidthSpace;</a></h2>
<p>出于好奇，笔者本机上使用<strong>Golang的encoding/json</strong>又做了一次测试：</p>
<ul>
<li>小写（Private）struct成员变量 <strong>310007 ops/s</strong></li>
<li>大写（Public）struct成员变量 <strong>163345 ops/s</strong></li>
<li>使用Tag指定对应关系（如下面的代码）：<strong>165207 ops/s</strong></li>
</ul>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">encoding/json</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> raw </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">".... json data..."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SimpleData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	ID         </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      `json:"id"`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	Name       </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `json:"name"`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	Data       []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `json:"data"`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	CreateTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `json:"createTime"`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	ModifyTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `json:"modifyTime"`</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BenchmarkJSONParse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">b</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">B</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b.N; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">		var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> data []</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SimpleData</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		json.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Unmarshal</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(raw, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">data)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><blockquote>
<p>go test -bench=. --benchtime=20s</p>
</blockquote>
<p>Golang在<strong>成员变量名与JSON属性不一样的情况下</strong>，性能测试结果比Java（Jackson） .Net Core（Newtonsoft.Json），Node/Deno（V8 JSON.parse）<strong>都要差</strong>，和Python的测试结果竟然几乎一样。使用<strong>同样的属性名时快一倍</strong>，略高于.Net Core，但实际上由于Golang通过变量大小写代替public/private的机制，<strong>造成了实际场景中，JSON解析往往需要指定Tag，多出来的这一层映射关系直接造成了损失一半性能</strong>。</p>
<p>JSON解析的性能很大程度上受所使用的库的影响，比如Golang有号称比encoding/json库快10倍的JSON-parser，Java也有号称最快JSON解析的阿里fastjson，JVM的<strong>字符串常量池</strong>机制也是<strong>单一数据</strong>情况下解析飞快的原因之一，因此这些数字并不代表什么，仅从这一点也并不能说明编程语言的优势劣势，毕竟<strong>PHP才是最好的编程语言</strong>。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[《SRE:Google运维解密》读后感]]></title>
            <link>https://code2life.top/blog/0041-goole-sre-thinking</link>
            <guid>https://code2life.top/blog/0041-goole-sre-thinking</guid>
            <pubDate>Sat, 04 May 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="《sre-google运维解密》读后感" tabindex="-1">《SRE:Google运维解密》读后感 <a class="header-anchor" href="#《sre-google运维解密》读后感" aria-label="Permalink to &quot;《SRE:Google运维解密》读后感&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>最近看了《SRE:Google运维解密》这本书，讲的是Google的天才们如何通过软件工程方法改变运维方式，保障超大型分布式系统的可靠性的。而这群人就是经验最丰富的<strong>Site Reliability Engineers</strong>，即最近 DevOps 大环境下比较热门的新职业 SRE。笔者已经很久没有遇到这种能一口气看完的书籍了，这些年本人各种<strong>勤杂工，临时工，消防员</strong>的经历，与书中很多地方都有强烈的共鸣，有种提壶灌顶的感觉，以至于忍不住想写一篇读后感，记录一下书中的经典语录和笔者个人的理解感受。</p>
<blockquote>
<p>大型软件系统生命周期的绝大部分都处于“使用”阶段，而非“设计”或“实现”阶段，那么为什么我们却总是认为软件工程应该首要关注设计和实现呢？</p>
</blockquote>
<p>这句话是书背面的第一句话，也是这句话引起了笔者的兴趣。作为一个软件开发者，我们很自然地对运维阶段和可靠性选择性失明，因为我们大部分精力关注在如何设计和实现了，即使在设计阶段考虑<strong>可用性，可伸缩性，性能，数据完整性</strong>等等各种质量属性，也肯容易<strong>陷入把这些非功能性的质量属性划到低于功能需求的较低优先级需求的误区中</strong>，在<strong>架构设计的权衡利弊上低估了Reliability的重要性</strong>。人们总是会说&quot;先实现功能&quot;，这句话就已经暴露了软件设计和开发人员的思维误区了。就如Google VP, SRE名词的创始人，Ben Treynor Sloss所说，<strong>“可靠性是任何产品设计中最基本的概念，任何一个系统如果没有人能够稳定的使用，就没有存在的意义”</strong>。</p>
<h2 id="devops-与-sre" tabindex="-1">DevOps 与 SRE <a class="header-anchor" href="#devops-与-sre" aria-label="Permalink to &quot;DevOps 与 SRE&quot;">&ZeroWidthSpace;</a></h2>
<p>Google 在全球构建了上千人的Site Reliability Engineer团队，融合了开发与运维，<strong>彻底解决了研发团队与运维团队的分歧和鸿沟</strong>。</p>
<p>可以说，<strong>DevOps 是 SRE 理念的普适版</strong>，<strong>SRE 是 Google 对于DevOps的具体实践</strong>，二者的核心理念是相通的。</p>
<p>第一章对研发与运维的矛盾根源的解释非常直白：**研发部门想要“随时随地的发布新功能，没有任何阻拦”，而运维部门则想要“一旦一个东西在生产环境正常工作了，就不要再进行任何改动”。**运维团队会列出一个非常长的检查清单，要求上线之前任何功能必须将所有以前的事故模拟一遍，开发团队吃过苦头后转为功能开关，增量更新，补丁化，这些名词的唯一目的就是绕过运维部门设立的各种流程。这种说法比较过激，但现实项目中确实有类似的感觉。</p>
<p>因为两个角色的<strong>关注点不同，对风险的定义不一致</strong>，为了维护各自的利益而产生矛盾。从根本上解决这样的矛盾就是让两个角色互相理解对方的思维和关注点，也就是需要开发和运维部门都要在思维上有所改变，在本职工作之上，从软件项目整体来思考。而SRE的目的就是<strong>寻求快速创新和高效运营之间的风险平衡，而不是简单的将服务在线时间最大化</strong>。</p>
<h2 id="基本概念" tabindex="-1">基本概念 <a class="header-anchor" href="#基本概念" aria-label="Permalink to &quot;基本概念&quot;">&ZeroWidthSpace;</a></h2>
<p>书中提到了很多概念和名词，用来<strong>明确及量化</strong>一些可用性以及其他相关的属性。</p>
<h4 id="风险容忍度" tabindex="-1">风险容忍度 <a class="header-anchor" href="#风险容忍度" aria-label="Permalink to &quot;风险容忍度&quot;">&ZeroWidthSpace;</a></h4>
<p>风险容忍度源于产品的商业目标，转化为工程目标，进而用于制定和量化性能和可靠性目标。简单来说就是量化<strong>系统挂了会带来多大的损失</strong>。波音飞机的Bug会导致坠机和乘客伤亡，风险容忍度就是0；提供企业服务的产品会一般比提供ToC产品的服务风险容忍度更低。</p>
<h4 id="服务质量指标-sli" tabindex="-1">服务质量指标 SLI <a class="header-anchor" href="#服务质量指标-sli" aria-label="Permalink to &quot;服务质量指标 SLI&quot;">&ZeroWidthSpace;</a></h4>
<p>Service-Level Indicator，可以理解为<strong>可度量的关键监控指标</strong>，可用性，延迟，错误率，吞吐量等等，这些指标度量了服务质量。</p>
<h4 id="服务质量目标-slo" tabindex="-1">服务质量目标 SLO <a class="header-anchor" href="#服务质量目标-slo" aria-label="Permalink to &quot;服务质量目标 SLO&quot;">&ZeroWidthSpace;</a></h4>
<p>Service-Level Objective，定义关键指标的<strong>预期目标</strong>，如99%的响应延迟&lt;100ms。如果SLI的真实数据超过了定义的预期SLO，甚至可以<strong>主动制造可控的故障</strong>，见中文版36页的”Chubby服务计划内停机“。SLO不需要定义的太高，造成成本过高或是服务极其稳定的假象，可以<strong>对外定义稍低的目标，对内定义较高的目标，来留出一定的安全缓冲区</strong>。</p>
<h4 id="服务质量协议-sla" tabindex="-1">服务质量协议 SLA <a class="header-anchor" href="#服务质量协议-sla" aria-label="Permalink to &quot;服务质量协议 SLA&quot;">&ZeroWidthSpace;</a></h4>
<p>Service-Level Agreement，是指跟用户达成的协议，是<strong>法律上与用户的协议条款</strong>，比如没有达到SLO的99.99%可用性会赔多少钱，这就是SLA。Google的GSuite的<a href="https://gsuite.google.com/intl/en/terms/sla.html" target="_blank" rel="noreferrer">SLA</a>写道：如果每个月的可用性没有达到99.9%，按照不可用时间赔偿Service Credit，也就是免费续用多少天；而Google的CDN和Cloud Storage这些业务定义的SLA是月度没达到99.95%则赔偿。</p>
<h4 id="错误预算" tabindex="-1">错误预算 <a class="header-anchor" href="#错误预算" aria-label="Permalink to &quot;错误预算&quot;">&ZeroWidthSpace;</a></h4>
<p>基于SLO计算出来的在某段时间还能容忍多久的故障，用这个<strong>具体的数字来决定是否可以在生产环境发布</strong>。</p>
<p>举例来说，比如上个季度一次可没有事故发生，那么在99.99%可用性的SLO下，还有12.96分钟的故障时间可以“挥霍”，并且这个时间足够发现事故并回滚变更，那么就允许一次全量发布。用数字说话来避免部门间的“谈判“和“博弈“，比如开发和销售部门想上线、运维部门不给上线这种情况的撕X。</p>
<h4 id="mttf-mttr" tabindex="-1">MTTF &amp; MTTR <a class="header-anchor" href="#mttf-mttr" aria-label="Permalink to &quot;MTTF &amp; MTTR&quot;">&ZeroWidthSpace;</a></h4>
<p>平均失败时间和平均恢复时间，当事故真的发生时，平均失败时间用于计算可用性SLO是否达标；平均恢复时间用来评价事故应急能力，量化处理故障的效率。</p>
<h2 id="sre思维" tabindex="-1">SRE思维 <a class="header-anchor" href="#sre思维" aria-label="Permalink to &quot;SRE思维&quot;">&ZeroWidthSpace;</a></h2>
<p>本节摘录了一些书中一些一针见血的SRE理念。以原书经典内容为标题，加上了一些笔者的理解和现实经历，从这些Google工程师简短朴实的原话中窥一斑而见全豹，理解SRE技术和管理思维。</p>
<h4 id="如果系统正常运转中需要人工干预-应该将此视为一种bug" tabindex="-1">如果系统正常运转中需要人工干预，应该将此视为一种Bug <a class="header-anchor" href="#如果系统正常运转中需要人工干预-应该将此视为一种bug" aria-label="Permalink to &quot;如果系统正常运转中需要人工干预，应该将此视为一种Bug&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>DevOps和SRE的核心理念之一就是<strong>自动化任何可以自动化的事情，尽可能的减少琐事</strong>，这样SRE或者运维才有精力去关注和优化整个工程，而不是做重复的或者无聊的琐事。SRE团队定了50%的上限，每个人必须有<strong>50%以上</strong>的时间用在软件开发或者其他有突破性价值的工作上</li>
<li>现实案例：Kubernetes以及其在Google的前身Borg把服务扩容做到了自动化，Prometheus以及Google的Borgmon把监控做到了自动发现自动监控都是成功案例</li>
</ul>
<h4 id="简化-直到不能再简化" tabindex="-1">简化，直到不能再简化 <a class="header-anchor" href="#简化-直到不能再简化" aria-label="Permalink to &quot;简化，直到不能再简化&quot;">&ZeroWidthSpace;</a></h4>
<p>这句话是针对监控和报警的，当然也适用于其他方面，这一小节我们也只关注监控预警的设计思路。</p>
<ul>
<li>“复杂是没有止境的，如果我们只能监控4个指标，就是<strong>延迟，流量，错误，饱和度</strong>”。</li>
<li>笔者曾一度认为监控预警多多益善，在故障时有足够的信息来找到根源问题，然而Google的做法颠覆了这个观念：<strong>在底层适当添加一些白盒监控的详细指标，在高层采用黑盒监控去监控真正对用户有影响的关键指标，避免虚警和过多监控数据造成根源误判</strong>。紧急警报应该关注于现象，而非试图在报警信息中推断原因。</li>
</ul>
<h4 id="可靠性只有靠最大程度的简化不断追求而得到" tabindex="-1">可靠性只有靠最大程度的简化不断追求而得到 <a class="header-anchor" href="#可靠性只有靠最大程度的简化不断追求而得到" aria-label="Permalink to &quot;可靠性只有靠最大程度的简化不断追求而得到&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>与上一条一样，越简单的东西越不会出错，分布式系统每增加一个组件，可靠性是指数级下降的。应该避免过度设计，简化组件数量和每个组件的复杂度，这带来的收益远比架构师庞大工整的设计图有用。</li>
<li>顺便看了下NoCode项目（也是一名Google工程师的杰作），已经快3万星星了：<a href="https://github.com/kelseyhightower/nocode" target="_blank" rel="noreferrer">The best way to write secure and reliable applications. Write nothing; deploy nowhere.</a></li>
<li>话说回来，软件本身的复杂度不可避免，但是通过<strong>把负代码行作为一个绩效指标，最小API原则</strong>来减缓复杂度的增长。相信每个开发工程师都经历过把某些代码删除，一切就恢复正常了的经历，反正笔者有很多这样的经历，最喜欢的事情之一就是删代码。</li>
</ul>
<h4 id="理解一个系统如何工作并不能使人成为专家-只能靠调查系统为何不能正常工作才行" tabindex="-1">理解一个系统如何工作并不能使人成为专家，只能靠调查系统为何不能正常工作才行 <a class="header-anchor" href="#理解一个系统如何工作并不能使人成为专家-只能靠调查系统为何不能正常工作才行" aria-label="Permalink to &quot;理解一个系统如何工作并不能使人成为专家，只能靠调查系统为何不能正常工作才行&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>“系统正常，只是改系统无数异常情况下的一种特例”，而解决故障是技术成长的”捷径“</li>
<li>笔者对这一点感触颇深，这几年技术成长最快的时候，不是开发或维护软件系统写了很多代码的时候，是那些<strong>痛苦的救火经历</strong>。比如之前有一次核心业务支撑不了百万级数据并发写入，从业务代码到数据库做性能优化的经历，让笔者对并发编程和数据库知识有了质的提升；而最近解决了某个项目非常隐蔽的Socket泄露问题带来的对网络传输层的理解超过了一个学期的课程。<strong>解决故障从来不是靠猜，靠搜索引擎，而是通过推理、编码、测试、验证</strong>，才能找到那些藏在深处的问题根源。<strong>纸上得来终觉浅，绝知此事要躬行</strong>。</li>
</ul>
<h4 id="东西早晚要坏的-这就是生活" tabindex="-1">东西早晚要坏的，这就是生活 <a class="header-anchor" href="#东西早晚要坏的-这就是生活" aria-label="Permalink to &quot;东西早晚要坏的，这就是生活&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这本书贯穿全书的思想就是<strong>故障一定会发生，而且大部分情况下是以没有人能预料的方式发生</strong>。因此我们需要：<strong>提前做故障演练，事先准备好应对措施，使用正确的方法论处理故障（第11到16章都是在阐述如何正确应对故障），并做好事后总结</strong>。</li>
<li>这句话其实与<strong>墨菲定律</strong>等价，概率论告诉我们：假设某意外事件在一次实验（活动）中发生的概率为p（p&gt;0），则在n次实验（活动）中至少有一次发生的概率为P=1-（1-p）^n。由此可见，无论概率p多么小（即小概率事件），当n越来越大时，P越来越接近1</li>
<li>业界有Netflix这样疯狂的公司在产线跑<a href="https://github.com/Netflix/chaosmonkey" target="_blank" rel="noreferrer">Chaos Monkey</a>,最近阿里也官宣了<a href="https://yq.aliyun.com/articles/695569" target="_blank" rel="noreferrer">Chaos Blade</a>，可见在微服务大潮下，<strong><a href="//principlesofchaos.org/" target="_blank" rel="noreferrer">混沌工程</a><strong>的重要性。大家意识到了故障的必然性，索性人为制造故障，强迫应用软件必须能够在</strong>随时故障的环境下提供可靠的服务</strong>。</li>
</ul>
<h4 id="如果你还没有亲自试过某件东西-那么就假设它是坏的" tabindex="-1">如果你还没有亲自试过某件东西，那么就假设它是坏的 <a class="header-anchor" href="#如果你还没有亲自试过某件东西-那么就假设它是坏的" aria-label="Permalink to &quot;如果你还没有亲自试过某件东西，那么就假设它是坏的&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这句话是第17章的引言，说明了<strong>测试的重要性</strong>。<strong>测试是工程师提高可靠性投入回报比最高的一种手段</strong>。测试驱动开发（TDD）这个词出现很久了，但是落到实处的却不多。<strong>大部分软件开发者，包括笔者自己，都没有意识到测试带来的巨大回报</strong>。单元测试，压力测试，集成测试，端到端测试等等自动化测试用例和测试代码的编写，带来的是随着时间增长指数级增长的回报，因为这些<strong>自动化测试不是运行一次，每天都可以运行，而且时间越长，软件复杂度越高的情况下，带来的收益将远超编写测试的时间成本</strong>。</li>
<li>人总是倾向贪婪算法获取短期利益，很难意识到风险其实是最昂贵的，一旦发生事故，带来的损失甚至是无法用金钱衡量的。建立<strong>强测试文化</strong>很难，但值得去做。</li>
</ul>
<h4 id="多样化的团队可以避免设计盲点" tabindex="-1">多样化的团队可以避免设计盲点 <a class="header-anchor" href="#多样化的团队可以避免设计盲点" aria-label="Permalink to &quot;多样化的团队可以避免设计盲点&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>SRE是需要开发自动化运维相关系统的，而开发团队的组建Google也给出了最佳实践：<strong>合并通用型人才（generalist）和领域专家（specialist）构建种子团队</strong>。</li>
<li>通用型人才可以很快开始工作，而资深领域专家可以提供更广阔的知识和经验。</li>
</ul>
<h4 id="将所有鸡蛋放在一个篮子里是引来灾难的最好办法" tabindex="-1">将所有鸡蛋放在一个篮子里是引来灾难的最好办法 <a class="header-anchor" href="#将所有鸡蛋放在一个篮子里是引来灾难的最好办法" aria-label="Permalink to &quot;将所有鸡蛋放在一个篮子里是引来灾难的最好办法&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这句话讲的是负载均衡以避免单点故障。分布式系统要保障可靠性，必须<strong>没有任何一个单点故障源</strong>。<strong>墨菲定律告诉我们可能故障的地方，一定会故障</strong>。从光纤链路的冗余，网络路由自适应，到前后端的负载均衡，基于共识算法存储一致性的数据，最主要的原因就是避免单点故障。</li>
</ul>
<h4 id="如果请求没有成功-以指数型延迟重试" tabindex="-1">如果请求没有成功，以指数型延迟重试 <a class="header-anchor" href="#如果请求没有成功-以指数型延迟重试" aria-label="Permalink to &quot;如果请求没有成功，以指数型延迟重试&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>在重试机制方面，开发者倾向简单的策略，而不是稍复杂一点<strong>Exponential Back-off Retry</strong>，然而从可靠性分析来看，这可能是<strong>致命的问题</strong>，不断的错误重试可能<strong>触发正反馈循环，导致系统过载，甚至连锁故障</strong>，指数型延迟重试，可以避免在出现短暂过载或非致命故障时，发生连锁反应。举个例子来说，一个Java应用服务可能一开始只是负载较高，触发了比较频繁的GC，但期间<strong>失败或超时的请求不断重试会加剧负载</strong>，GC更加频繁，导致重试更多，最终触发<strong>GC死亡螺旋</strong>，导致系统崩溃。</li>
<li>关于如何避免<strong>过载</strong>和<strong>连锁故障</strong>，在第22章有很多深入技术细节的讨论，见中文版260页。笔者印象最深刻的一次连锁故障就是<strong>调用 Redis 的 KEYS</strong> 导致业务停滞和缓存击穿，本质上还是BUG导致系统资源不足和关键组件过载，而客户端并未降级，还是以固定频率发送请求，血淋淋的教训啊。</li>
<li>另外，分布式系统很容易出现<strong>惊群效应</strong>导致<strong>资源白白浪费甚至系统过载</strong>，这在第24章<strong>分布式周期性任务</strong>中有一些相关讨论非常有借鉴意义，比如分离Workflow状态到一致性存储，无状态幂等的Job等。</li>
</ul>
<h4 id="很多分布式系统的问题最后都归结为分布式共识问题的不同变种" tabindex="-1">很多分布式系统的问题最后都归结为分布式共识问题的不同变种 <a class="header-anchor" href="#很多分布式系统的问题最后都归结为分布式共识问题的不同变种" aria-label="Permalink to &quot;很多分布式系统的问题最后都归结为分布式共识问题的不同变种&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>看到这句话才意识到，<strong>分布式锁，强一致存储，复制状态机(RSM)，可靠的消息队列</strong>，其实本质是一样的，归结于CAP的取舍，为了让不同节点的数据达成共识，比如RDBMS为了事务ACID而取CA，而NoSQL只提供BASE而取AP或CP，他们在分布式部署时的本质都是RSM</li>
</ul>
<h4 id="最好的数据完整性保障手段一定是多层的-多个保障手段彼此覆盖" tabindex="-1">最好的数据完整性保障手段一定是多层的，多个保障手段彼此覆盖 <a class="header-anchor" href="#最好的数据完整性保障手段一定是多层的-多个保障手段彼此覆盖" aria-label="Permalink to &quot;最好的数据完整性保障手段一定是多层的，多个保障手段彼此覆盖&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这也是书中多次提到的”<strong>纵深防御</strong>“策略，<strong>数据完整性</strong>也是软件设计阶段容易忽视的一个属性，而恰巧这<strong>对用户来说通常是最重要的属性之一，没有用户可以容忍自己的数据平白无故消失了</strong>。</li>
<li>纵深防御的常用多层防御策略有：<strong>软删除，备份和恢复，数据复制和冗余</strong>等等，这些策略适用于<strong>应用软件，操作系统和硬件</strong>层面。举例来说，就是不能因为有RAID机制而不去做应用层的数据复制，不能因为有定期备份而不做软删除。看到这里笔者<strong>悄悄地</strong>给检查了一下最近一个新项目的数据库设计，给几个关键表加上了“deleted tinyint(1)”字段。</li>
</ul>
<h4 id="忘记过去的人必然会犯同样的错误" tabindex="-1">忘记过去的人必然会犯同样的错误 <a class="header-anchor" href="#忘记过去的人必然会犯同样的错误" aria-label="Permalink to &quot;忘记过去的人必然会犯同样的错误&quot;">&ZeroWidthSpace;</a></h4>
<p>这句话是在书中是在故障演练和事后总结这一部分提到的，SRE需要牵头演练过去发生的和以后可能发生的故障。</p>
<ul>
<li>SRE需要把<strong>故障演练</strong>以及<strong>事后总结</strong>作为工作的一部分，每年或每个季度在非生产环境，甚至部分故障容忍度稍高的生成环境上，演练故障和恢复流程。<strong>既然故障一定会发生，那么是接受一次可控的演习故障发现潜在问题，还是在半夜慌忙处理真实发生的故障呢？</strong></li>
<li>故障演习的好处太多，但不代表真实故障不会发生，对于真实发生的故障，<strong>每一次故障都是非常宝贵的经验</strong>，应该重视事后总结，并互相学习这些事故。这一点非常佩服 Google 的SRE们，在书中<strong>毫不避讳地分享了很多真实的大规模产线故障，以及透彻事后总结分析</strong>。</li>
</ul>
<h4 id="一次中断性任务需要进行两次上下文切换-而这种切换会造成数个小时的生产力丧失" tabindex="-1">一次中断性任务需要进行两次上下文切换，而这种切换会造成数个小时的生产力丧失 <a class="header-anchor" href="#一次中断性任务需要进行两次上下文切换-而这种切换会造成数个小时的生产力丧失" aria-label="Permalink to &quot;一次中断性任务需要进行两次上下文切换，而这种切换会造成数个小时的生产力丧失&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这是一条管理方面的观念：管理者需要意识到人的优势和劣势，人的效率不是稳定的。从人的心理学角度来看，人进入<strong>Flow State</strong>可以提升生产力，甚至艺术创造性，这就是<strong>专注的力量</strong>。而<strong>中断性任务</strong>，也就是各种<strong>会议，工单，临时事务，他人咨询和技术讨论</strong>等等，造成的<strong>上下文切换，会导致效率的成倍降低</strong>。这也是当前一些开发者抱怨白天干不了活，晚上加班干活效率才高的原因。而<strong>SRE兼职运维角色</strong>，必然有工单，常规运维负载等等中断性任务，很容易陷入低效循环中。</li>
<li>Google解决这一问题的办法就是，<strong>极化时间，合理的轮值机制和琐事度量</strong>。极化时间是和轮值轮岗相辅相成的，比如今天某人专职负责处理工单和常规运维，代表参加其他团队的会议等等，明天专注X自动化运维系统的开发，由其他成员负责这些事情。当然这对团队成员的能力要求会更高，更彻底的做法就是<strong>尽可能减少这些“琐事”</strong>。</li>
</ul>
<h4 id="永远不要在正在运行的系统上做改动" tabindex="-1">永远不要在正在运行的系统上做改动 <a class="header-anchor" href="#永远不要在正在运行的系统上做改动" aria-label="Permalink to &quot;永远不要在正在运行的系统上做改动&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>这句话的语境是在说<strong>金丝雀测试和灰度发布</strong>，也可以叫“渐进式发布”，Google有专门的<strong>发布协调工程师（LCE）<strong>负责软件发布，以及</strong>量化计算的灰度比例</strong>。在生产环境上直接全量修改版本或配置，无异于<strong>直接制造灾难</strong>。</li>
<li>在部署变更和灰度控制过程中，有一个非常重要的概念，<strong>“跛脚鸭状态”</strong>，即不接受新的请求，处理完当前的任务即下线。跛脚鸭效应这个词源于政界的换届选举，用来描述<strong>应用服务的优雅退出</strong>可以说是非常形象了。</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>这本书从思维，技术，协作，管理等各个方面剖析了软件的可靠性，<strong>传统的开发人员和运维人员往往跳不出自己的职业思维</strong>，读完这本书后可以从整体上来看待软件生命周期。<strong>开发，测试，运维，管理者去看，都会有不同的收获</strong>。附一张系统可用性时间表：</p>
<p><img src="//filecdn.code2life.top/sla.png" alt=""></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[ServiceMesh] 服务网格istio入门实践]]></title>
            <link>https://code2life.top/blog/0033-istio-trial</link>
            <guid>https://code2life.top/blog/0033-istio-trial</guid>
            <pubDate>Fri, 03 May 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="servicemesh-服务网格istio入门实践" tabindex="-1">[ServiceMesh] 服务网格istio入门实践 <a class="header-anchor" href="#servicemesh-服务网格istio入门实践" aria-label="Permalink to &quot;[ServiceMesh] 服务网格istio入门实践&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>2020年更新：由于 istio 1.5 发生了重大的架构改动，本篇小部分内容与当前最新版本不完全一致。istio目前仍然处于不太成熟的阶段，最近出现了一下eBPF模式的无代理ServiceMesh，也值得关注。</p>
</blockquote>
<h2 id="浅谈服务网格" tabindex="-1">浅谈服务网格 <a class="header-anchor" href="#浅谈服务网格" aria-label="Permalink to &quot;浅谈服务网格&quot;">&ZeroWidthSpace;</a></h2>
<p>服务网格即Service Mesh，istio是目前最主流的Service Mesh实现。笔者第一次了解istio是18年3月份，那时候istio 0.7刚发布，硬着头皮读完官方文档之后内心是崩溃的，这啥玩意？之后逐渐了解一些背景知识之后，才开始对istio以及服务网格有了一些浅显的理解。</p>
<h3 id="服务网格的概念理解" tabindex="-1">服务网格的概念理解 <a class="header-anchor" href="#服务网格的概念理解" aria-label="Permalink to &quot;服务网格的概念理解&quot;">&ZeroWidthSpace;</a></h3>
<p>首先，看下istio提供了什么：</p>
<ul>
<li>服务间通信的基础设施层, 服务治理的<strong>终极解决方案</strong></li>
<li><strong>平台级别</strong>的服务发现，流量管理控制，以istio为例，它提供的能力包括：
<ul>
<li>TCP/HTTP/HTTP2/gRPC 应用的服务发现、负载均衡</li>
<li>出入流量控制和路由、TLS/双向TLS、认证授权、黑白名单</li>
<li>灰度控制、限流、熔断</li>
<li>健康检查、故障注入、镜像流量</li>
<li>对应用无感知的监控、日志、链路追踪、动态服务拓扑图等等</li>
</ul>
</li>
</ul>
<p>罗列这些功能之后可能还是不好理解Service Mesh，理解一个抽象的概念最好的办法就是具象化。我们把微服务系统想象成一个巨大的工厂，生产出的产品就相当于微服务系统对外部提供的服务，每个微服务应用想象成工厂流水线上的一个个工人，那么如何保障这个复杂的流水线正常运行呢？从每个岗位上的工人的角度，有两种做法：</p>
<ul>
<li>一种做法是把自己要做的事情做好，并且跟上下游的每个工人保持沟通，明确当前步骤完成后交给下游谁去继续处理（负载均衡），下游工人请假了怎么办（熔断降级）等等</li>
<li>另一种做法把自己要做的事情做好，其它的所有事情交给流水线管理员和&quot;智能流水线&quot;去处理，不需要直接跟上下游任何其它工人沟通</li>
</ul>
<p>很显然，相比于第二种方法，第一种做法是低效的。第二种方法每个工人<strong>只需要专注于自己的工作，无须考虑任何其它事情</strong>，效率更高也更智能。istio就像这样的“智能流水线管理系统”，从接管流水线的<strong>出入口（流量）为切入点</strong>来治理整个生产链路，<strong>它不处理生产制造本身，但处理一切与生产制造无关的事务</strong></p>
<p>我们回到软件工程领域，软件的复杂度是永恒的，<strong>没有银弹可以”降低复杂度“，只有将其分而治之</strong>。大到领域驱动设计，微服务架构，小到每个方法/函数的切分重构，都是这个道理。微服务化解决了很多问题同时也带来了另外的问题，Kubernetes通过抽象出Service提供服务发现和简单的负载均衡，Ingress提供API网关，解决了部分服务治理的问题。然而仅仅这些还不够，<strong>ServiceMesh就是一个完整的解决服务治理问题的思路，istio则是主流的实现之一</strong>。</p>
<h2 id="istio-初探" tabindex="-1">istio 初探 <a class="header-anchor" href="#istio-初探" aria-label="Permalink to &quot;istio 初探&quot;">&ZeroWidthSpace;</a></h2>
<p>istio通过每个服务配备一层代理（SideCar）来接管服务的出入口流量，以此来解耦业务逻辑和服务治理，不影响服务本身的业务。下面逐一介绍istio中的组件以及使用方法。</p>
<h3 id="istio组件结构" tabindex="-1">istio组件结构 <a class="header-anchor" href="#istio组件结构" aria-label="Permalink to &quot;istio组件结构&quot;">&ZeroWidthSpace;</a></h3>
<p><img src="//filecdn.code2life.top/istio-arch.svg" alt=""></p>
<p>一图胜千言，这张图摘自官方文档： <a href="https://istio.io/docs/concepts/what-is-istio" target="_blank" rel="noreferrer">https://istio.io/docs/concepts/what-is-istio</a>  上半部分是<strong>数据平面</strong>，即代理服务出入流量的Envoy；下半部分是<strong>控制平面</strong>，包括一堆职责不同的组件，每个组件的功能和名字相对应，共同构成了<strong>Control Plane</strong>部分。</p>
<p><strong>数据平面</strong>：</p>
<ul>
<li><strong>Envoy</strong>：Envoy是一个<strong>成熟的</strong>C++编写的高性能流量代理组件，相对于Nginx更加灵活和完善，更适合在可伸缩的动态环境下使用。文档参考：<a href="https://www.envoyproxy.io/docs/envoy/latest/" target="_blank" rel="noreferrer">https://www.envoyproxy.io/docs/envoy/latest/</a></li>
</ul>
<p><strong>控制平面</strong>：</p>
<ul>
<li><strong>Pilot</strong>：翻译成“<strong>领航员</strong>”更合适，Pilot监视着服务的实例，给Envoy提供<strong>服务发现</strong>的能力（这层服务发现是建立在K8S Service之上的，并不等价于K8S的服务发现机制），并生成流量控制的配置，动态推送流量控制配置到Envoy上实现<strong>智能路由</strong>。</li>
<li><strong>Citadel</strong>：“堡垒”，顾名思义，Citadel管理着通信安全，将TLS证书管理起来并推送相应的配置给Envoy，Envoy使用这些配置来实现<strong>TLS/双向TLS的加载和终止</strong>，业务服务无需关心通信安全。</li>
<li><strong>Mixer</strong>：“混合器”，Mixer<strong>收集Envoy上的遥测信息</strong>，交给Adapter处理，并且兼具管理和实现<strong>限速，黑白名单等策略</strong>的功能。这些Adapter实现请求链路追踪信息的持久化，日志的收集，监控数据的收集等等。这个“混音器”的主要作用就是“混合”第三方组件，比如集成Prometheus，Jaeger，Fluentd等等，实现istio本身不具备的功能。</li>
<li><strong>Galley</strong>：可以翻译成“战舰”，目前作为<strong>配置校验</strong>的组件，根据官网介绍，这个组件的最终目标是将与底层平台相关的东西抽到这里，比如Kubernetes相关的配置获取。</li>
</ul>
<h3 id="在kubernetes集群安装istio" tabindex="-1">在Kubernetes集群安装istio <a class="header-anchor" href="#在kubernetes集群安装istio" aria-label="Permalink to &quot;在Kubernetes集群安装istio&quot;">&ZeroWidthSpace;</a></h3>
<p>istio的安装请参考官方文档，下载Release版本，把Yaml文件Apply到Kubernetes集群中即可。<a href="https://github.com/istio/istio/releases" target="_blank" rel="noreferrer">https://github.com/istio/istio/releases</a></p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 下载解压后进入目录应用该Yaml</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./install/kubernetes/istio-demo.yaml</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 看一下istio-system的组件</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> istio-system</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#NAME                                      READY   STATUS      </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#grafana-67c69bb567-hhlz6                  1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-citadel-78c9c8b75f-wkkcv            1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-cleanup-secrets-1.1.4-2skw8         0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-cleanup-secrets-2rwf8               0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-egressgateway-6df84c5bd4-ddrrc      1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-galley-65fc98ffd4-mmw54             1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-grafana-post-install-1.1.4-bqdj2    0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-grafana-post-install-2c2q9          0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-ingressgateway-78f9cbb78f-crtsh     1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-pilot-6b75486f59-vm4zm              2/2     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-policy-784c66bc85-zqg4x             2/2     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-security-post-install-1.1.4-s9r44   0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-security-post-install-zlfcx         0/1     Completed   </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-sidecar-injector-bf946798-llpql     1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-telemetry-8476d56f55-kc8lp          2/2     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#istio-tracing-5d8f57c8ff-9mmsj            1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#kiali-d4d886dd7-hcj72                     1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#prometheus-5554746896-hf9fh               1/1     Running     </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#servicegraph-5c4485945b-4hdw8             1/1     Running     </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 还可以通过这条命令查看istio创建出来的CustomResourceDefinitions</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> crd</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># istio会创建数十个CRD，有一些核心的CRD下面会讲解</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 创建BookInfo示例项目</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./samples/bookinfo/platform/kube/bookinfo.yaml</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 在default Namespace下可以看到对应的Pod</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 每个Pod有一个应用服务容器和一个SideCar容器</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><p>可以看到，实际部署中还会有基于Envoy的<strong>IngressGateway，EgressGateway</strong>，另外有一些诸如<strong>istio-tracing</strong>（Jaeger）、以及可视化工具<strong>service-graph，Kiali</strong>等等。</p>
<p>注意：Kubernetes 1.9之后能够支持<strong>MutatingAdmissionWebhook</strong>，而<strong>istio-sidecar-injector</strong>会利用这个特性通过Webhook<strong>自动注入SideCar容器到Pod中</strong>，这个特性需要API Server启动参数添加：<strong>--admission-control=MutatingAdmissionWebhook, ValidatingAdmissionWebhook</strong>。笔者用的Minikube在本地实验，默认已经支持，无需额外配置。</p>
<p>具体内容参考：<a href="https://istio.io/docs/setup/kubernetes/install/kubernetes/" target="_blank" rel="noreferrer">https://istio.io/docs/setup/kubernetes/install/kubernetes/</a></p>
<h3 id="流量管理" tabindex="-1">流量管理 <a class="header-anchor" href="#流量管理" aria-label="Permalink to &quot;流量管理&quot;">&ZeroWidthSpace;</a></h3>
<p>流量管理是istio的<strong>核心功能</strong>，我们以示例项目BookInfo （<a href="https://istio.io/zh/docs/examples/bookinfo/" target="_blank" rel="noreferrer">https://istio.io/zh/docs/examples/bookinfo/</a>），从最简单的请求路由开始，逐步了解Pilot与Envoy组合的黑科技。</p>
<h4 id="基础路由配置" tabindex="-1">基础路由配置 <a class="header-anchor" href="#基础路由配置" aria-label="Permalink to &quot;基础路由配置&quot;">&ZeroWidthSpace;</a></h4>
<p>首先，我们要给BookInfo应用创建一个入口告诉IngressGateway，这个入口就是一个<strong>Gateway</strong>。Gateway是istio创建的众多<strong>Kubernetes CRD</strong>之一，一个Gateway资源，可以类比Nginx网关的<strong>server部分</strong>配置。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># bookinfo-gateway.yaml </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">bookinfo-gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  selector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    istio</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ingressgateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  servers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">80</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">http</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">HTTP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"*"</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><p>有了Gateway之后，继续定义<strong>VirtualService</strong>，应用到IngressGateway的Envoy上。下面这个绑定了Gateway的虚拟服务相当于Nginx的<strong>location部分</strong>，upstream是//productpage:9080。</p>
<blockquote>
<p>VirtualService 定义了一系列针对指定服务的流量路由规则。每个路由规则都针对特定协议的匹配规则。如果流量符合这些特征，就会根据规则发送到服务注册表中的目标服务（或者目标服务的子集或版本）</p>
</blockquote>
<p>也就是说，VirtualService是流量发起方的控制，根据请求的host匹配到对应的VirtualService，再应用对应的策略。VirtualService可以定义的策略非常丰富，下面会逐一讲解。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># bookinfo-edge-service.yaml </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">bookinfo</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"*"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  gateways</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">bookinfo-gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uri</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        exact</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/productpage</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uri</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        exact</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/login</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uri</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        exact</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/logout</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uri</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        prefix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/api/v1/products</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">9080</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br></div></div><p>&quot;bookinfo&quot;这个VirtualService定义了几个URL的目的地Destination，即productpage前端服务，productpage进而需要与后端reviews、details两个服务通信，而reviews又需要和ratings通信。</p>
<p>这时候，我们需要创建<strong>DestinationRule</strong>，一方面我们需要知道这些服务在K8S集群的真实域名是什么，即host；而另一方面这些后端服务可能是多版本并存的，又需要定义每个版本对应的负载均衡子集（<strong>Subset</strong>）的Label Selector。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># destination-rule-all.yaml </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">DestinationRule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  subsets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">DestinationRule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  subsets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v3</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">DestinationRule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ratings</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ratings</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  subsets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">DestinationRule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">details</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">details</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  subsets</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br></div></div><p>DestinationRule还有一些高级功能，比如<strong>TrafficPolicy</strong>，可以定义负载均衡算法，负载均衡池的健康检查规则，TLS配置，HTTP或TCP连接池的限制等等。</p>
<p>定义好DestinationRules之后，继续来定义所有服务的<strong>VirtualService</strong>了，这些VirtualService<strong>没有gateways</strong>，会有默认的gateways值&quot;<strong>mesh</strong>&quot;，即对所有网格里<strong>SideCar</strong>的Envoy生效，而<strong>非IngressGateway</strong>生效）</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># virtual-service-all-v1.yaml</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">productpage</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ratings</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ratings</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ratings</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">details</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">details</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">details</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br></div></div><p>至此，我们已经把BookInfo的子服务之间的路由和通信规则定义好了，命令以及输出如下：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bookinfo-gateway.yaml</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#gateway.networking.istio.io/bookinfo-gateway created</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bookinfo-edge-service.yaml</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#virtualservice.networking.istio.io/bookinfo created</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> destination-rule-all.yaml</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#destinationrule.networking.istio.io/productpage created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#destinationrule.networking.istio.io/reviews created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#destinationrule.networking.istio.io/ratings created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#destinationrule.networking.istio.io/details created</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> virtual-service-all-v1.yaml</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#virtualservice.networking.istio.io/productpage created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#virtualservice.networking.istio.io/reviews created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#virtualservice.networking.istio.io/ratings created</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#virtualservice.networking.istio.io/details created</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>现在可以试着从浏览器访问**//&lt;node-ip&gt;:&lt;NodePort&gt;/productpage**即可看到BookInfo应用的页面，在此之前需要先找下IngressGateway的外部访问入口：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Step1. 查看K8S集群物理机/虚拟机边缘节点的真实IP</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">minikube</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 非minikube环境通过Node信息找 External-IP</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> no</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wide</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Step2. 找到IngressGateway的LoadBalancer定义</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 这里NodePort：http2 31380 就是对外暴露的服务端口</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> svc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> istio-ingressgateway</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> istio-system</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Type:                     LoadBalancer</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#IP:                       10.106.24.49</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Port:                     status-port  15020/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#TargetPort:               15020/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#NodePort:                 status-port  30047/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Endpoints:                172.17.0.38:15020</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Port:                     http2  80/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#TargetPort:               80/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#NodePort:                 http2  31380/TCP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Endpoints:                172.17.0.38:80</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># ... 此处省略一串输出</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><p>除了BookInfo应用之外，IngressGateway上还有很多其它的端口，可以自行<strong>创建Gateway把istio-system的组件暴露出去</strong>，比如Kiali：</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 创建一个绑定到15029端口的Gateway，并路由到 Kiali Service对应的Endpoint上</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 注意访问外部访问端口不是15029，是相应的NodePort 30294</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kiali-gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  selector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    istio</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ingressgateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  servers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">15029</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">http</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">HTTP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"*"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">networking.istio.io/v1alpha3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">VirtualService</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kiali-service</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  hosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"*"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  gateways</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kiali-gateway</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kiali</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">20001</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><p>浏览器访问**//&lt;node-ip&gt;:30294/kiali**, 默认用户名密码都是admin，可以看到Kiali Dashboard，其中<strong>Service Graph</strong>的功能相当惊艳，能够<strong>实时监控整个微服务系统的流量</strong>，比1.0版本之前的/servicegraph页面强大很多。</p>
<p><img src="//filecdn.code2life.top/kiali-service-graph.png" alt=""></p>
<p>另外，定义集群<strong>出口流量规则</strong>需要用到<strong>ServiceEntry</strong>，这也是非常重要的一个概念，但本文篇幅有限，<strong>EgressGateway</strong>以及出口流量控制相关功能暂不涉及。</p>
<h4 id="超时、重试、灰度控制、熔断降级、故障注入" tabindex="-1">超时、重试、灰度控制、熔断降级、故障注入 <a class="header-anchor" href="#超时、重试、灰度控制、熔断降级、故障注入" aria-label="Permalink to &quot;超时、重试、灰度控制、熔断降级、故障注入&quot;">&ZeroWidthSpace;</a></h4>
<p>看到这里，估计大部分读者有和笔者第一次试用istio同样的感受，这玩意咋配个路由都这么麻烦，<strong>Nginx几行搞定的事情却整这么复杂</strong>！</p>
<p>这是因为我们用Yaml把<strong>服务网格的模型</strong>建好了，之后所有高级功能，只需要几行即可实现，与在Kubernetes集群中部署服务一样，<strong>先苦后甜</strong>。</p>
<p><strong>超时和重试</strong>：Reviews服务的/newcatalog接口，最多允许10秒超时，如果请求发送失败重试3次，每次2秒超时（重试间隔自动指数级延迟，目前无法配置）</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Reviews服务的VirtualService配置部分</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uri</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        prefix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/newcatalog</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    timeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">10s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    retries</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      attempts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      perTryTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2s</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p><strong>灰度控制</strong>： 对于HTTP Header end-user为jason的所有请求到V2，其它的请求 25%的流量到V2版本，75% 到V1。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Reviews服务的VirtualService配置部分：</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">headers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        end-user</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          exact</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">jason</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  # 上面的match都没有匹配之后的default配置</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">route</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v2</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      weight</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">25</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        subset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      weight</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">75</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 注：在上面已经事先定义了DestinationRule，</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 并且Review-V2这个Deployment已经存在</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><p><strong>熔断降级</strong>：</p>
<ul>
<li>至多允许到Reviews服务10个并发连接、每个连接至多20个并发请求、未处理完的请求总和不能超过100个，否则触发断路器</li>
<li>对于1秒连续出现3个错误的实例，移出负载均衡池3分钟</li>
</ul>
<p>注：istio提供的断路器功能不涉及业务，业务逻辑仍要处理<strong>熔断后的503响应来返回降级的数据</strong></p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Reviews服务的DestinationRule配置部分</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  host</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">reviews</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  trafficPolicy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 连接限制策略</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    connectionPool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      tcp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        maxConnections</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        http1MaxPendingRequests</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        maxRequestsPerConnection</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">20</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 负载均衡池检查策略    </span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    outlierDetection</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      consecutiveErrors</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      interval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">1s</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      baseEjectionTime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">3m</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      maxEjectionPercent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p><strong>故障注入</strong>：对于Reviews服务，20%的请求使用400错误直接返回；10%的概率注入5秒延迟；</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Reviews服务对应的VirtualService部分配置</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  http</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">fault</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      abort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        percent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">20</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        httpStatus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      delay</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        percent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        fixedDelay</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">5s</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>另外还有<strong>请求数据重写，重定向、镜像流量</strong>等功能，可参考文档配置： <a href="https://istio.io/zh/docs/reference/config/istio.networking.v1alpha3/#httproute" target="_blank" rel="noreferrer">https://istio.io/zh/docs/reference/config/istio.networking.v1alpha3/#httproute</a></p>
<h3 id="mixer的超能力-监控、日志、链路追踪" tabindex="-1">Mixer的超能力：监控、日志、链路追踪 <a class="header-anchor" href="#mixer的超能力-监控、日志、链路追踪" aria-label="Permalink to &quot;Mixer的超能力：监控、日志、链路追踪&quot;">&ZeroWidthSpace;</a></h3>
<h4 id="监控" tabindex="-1">监控 <a class="header-anchor" href="#监控" aria-label="Permalink to &quot;监控&quot;">&ZeroWidthSpace;</a></h4>
<p>按照Demo装好之后，打开Prometheus的/config和/target页面，竟然已经有了很多监控配置，打开Grafana能看到很多漂亮的监控图表，那么这些监控数据是哪里来的呢？</p>
<p><img src="//filecdn.code2life.top/istio-grafana.png" alt=""></p>
<p>这需要从Mixer的原理说起，Mixer从Envoy的流量中提取属性，生成Instance，然后把它交给handler，而Rule就是告诉Mixer需要把哪个Instance发给哪个Handler。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 查看一下默认定义了哪些Rule</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rule</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> istio-system</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 以promhttp这个Rule为例</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rule</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> promhttp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yaml</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> istio-system</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>当请求符合Match的条件时，把requestcount等metric的实例发送给Prometheus Handler。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">rule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">promhttp</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  actions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">handler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">prometheus</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    instances</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">requestcount.metric</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">requestduration.metric</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">requestsize.metric</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">responsesize.metric</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">(context.protocol == "http" || context.protocol == "grpc") &#x26;&#x26; (match((request.useragent</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    |</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic"> "-"), "kube-probe*") == false)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>继续查看metric，发现requestcount.metric这个Instance就是把请求的属性抽出来，value设置为1。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># kubectl get metric requestcount -n istio-system -o yaml</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">metric</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">requestcount</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  dimensions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    connection_security_policy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">conditional((context.reporter.kind | "inbound") ==</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "outbound"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"unknown"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">conditional(connection.mtls | false, "mutual_tls", "none"))</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    destination_app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">destination.labels["app"] | "unknown"</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 后面省略...</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1"</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>进一步查看Handler，发现这个instance被发送到了一个叫prometheus的compiledAdapter。Prometheus Server通过事先配置好的Job，通过服务发现找到Mixer组件的/metrics接口刮取监控数据。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># kubectl get handler prometheus -o yaml -n istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">handler</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  compiledAdapter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">prometheus</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  params</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    metrics</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">instance_name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">requestcount.metric.istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">COUNTER</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    # 后面省略...</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><h4 id="日志" tabindex="-1">日志 <a class="header-anchor" href="#日志" aria-label="Permalink to &quot;日志&quot;">&ZeroWidthSpace;</a></h4>
<p>日志的原理也是一样的，Rule，Instance，Handler三步走。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># logentry Instance配置，定义日志提取模板</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"config.istio.io/v1alpha2"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">logentry</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">newlog</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  severity</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'"info"'</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  timestamp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">request.time</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  variables</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    source</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">source.labels["app"] | source.workload.name | "unknown"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    user</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">source.user | "unknown"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    destination</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">destination.labels["app"] | destination.workload.name | "unknown"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    responseCode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">response.code | 0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    responseSize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">response.size | 0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    latency</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">response.duration | "0ms"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  monitored_resource_type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'"UNSPECIFIED"'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Fluentd handler 配置</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"config.istio.io/v1alpha2"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">fluentd</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">handler</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  address</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"fluentd-es.logging:24224"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 定义Rule，所有请求提取newlog Instance发送给fluentd Handler</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"config.istio.io/v1alpha2"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">rule</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">newlogtofluentd</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">istio-system</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  match</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  actions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">   - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">handler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">handler.fluentd</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">     instances</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">     - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">newlog.logentry</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br></div></div><p>Apply到集群之后，在ElasticSearch相应的Kibana上，就可以查询到所有请求的日志了。</p>
<p><img src="//filecdn.code2life.top/istio-es.png" alt=""></p>
<h4 id="分布式链路追踪" tabindex="-1">分布式链路追踪 <a class="header-anchor" href="#分布式链路追踪" aria-label="Permalink to &quot;分布式链路追踪&quot;">&ZeroWidthSpace;</a></h4>
<p>默认配置下istio会追踪所有请求，可以通过修改Pilot的环境变量PILOT_TRACE_SAMPLING来降低采样率。BookInfo示例应用直接访问即可在Jaeger中查询到全链路信息。</p>
<p><img src="//filecdn.code2life.top/istio-tracing.png" alt=""></p>
<h4 id="其他功能" tabindex="-1">其他功能 <a class="header-anchor" href="#其他功能" aria-label="Permalink to &quot;其他功能&quot;">&ZeroWidthSpace;</a></h4>
<p>除了这些，Mixer还有策略检查的功能，可以实现<strong>对不同用户请求限速，黑白名单</strong>等功能。这些功能的原理都是相同的，根据Rule和请求生成Instance给Handler处理。</p>
<h3 id="通信安全" tabindex="-1">通信安全 <a class="header-anchor" href="#通信安全" aria-label="Permalink to &quot;通信安全&quot;">&ZeroWidthSpace;</a></h3>
<p>istio另一个亮点就是通信安全，主要有这几块，笔者还没学完，此处不在展开：</p>
<ul>
<li><strong>TLS/mTLS</strong>: SideCar代理可以轻易实现TLS/双向TLS的Loading和Off-Loading，让网格间的通信没有明文</li>
<li><strong>RBAC</strong> ： 基于角色的访问控制。istio定义了两个CRD： ServiceRole 和 ServiceRoleBinding，解决了同一个集群中如何控制A应该访问B，但不应该访问C的问题</li>
<li><strong>认证授权</strong>： 另一个CRD：Policy，提供请求流量在到达业务层之前就做好认证授权的能力，比如OAuth的集成和JWT认证。</li>
</ul>
<p>文档链接：<a href="https://istio.io/docs/tasks/security/" target="_blank" rel="noreferrer">https://istio.io/docs/tasks/security/</a></p>
<h2 id="总结与思考" tabindex="-1">总结与思考 <a class="header-anchor" href="#总结与思考" aria-label="Permalink to &quot;总结与思考&quot;">&ZeroWidthSpace;</a></h2>
<h3 id="istio与kubernetes" tabindex="-1">istio与Kubernetes <a class="header-anchor" href="#istio与kubernetes" aria-label="Permalink to &quot;istio与Kubernetes&quot;">&ZeroWidthSpace;</a></h3>
<p>istio虽然并不依赖Kubernetes，也可以在裸机上结合Consul实现服务治理能力，但是实际使用中，istio + Kubernetes 显然是一个最优解。因为istio的思路和Kubernetes很像，<strong>定义一系列抽象概念来脱离底层实现，把这些概念当做可以增删改查的资源声明式地管理</strong>，比如常用的几种：</p>
<ul>
<li>Gateway 定义了入口路由规则</li>
<li>VirtualService 定义了请求处理策略</li>
<li>DestinationRule 定义了目标负载均衡池的行为</li>
<li>ServiceEntry 定义与外部服务的通信规则</li>
</ul>
<p>在Kubernetes中，这些istio的配置<strong>元数据寄存于Etcd集群</strong>，使得istio的关键组件得以<strong>无状态化</strong>。另一方面，这些<strong>CustomResourceDefinition</strong>的资源又可以借助Kubernetes <strong>API Server</strong>的能力<strong>对外提供一致的管理接口</strong>。只是浅显地了解这些顶级开源项目，就收获颇多了，真的很佩服这些业界大佬们。</p>
<h3 id="istio与spring-cloud" tabindex="-1">istio与Spring Cloud <a class="header-anchor" href="#istio与spring-cloud" aria-label="Permalink to &quot;istio与Spring Cloud&quot;">&ZeroWidthSpace;</a></h3>
<p>在istio出现之前，Spring Cloud作为微服务领域的先行者，以丰富的生态已经占据了不小的市场，而ServiceMesh的兴起有点像对Spring Cloud的<strong>降维打击</strong>。传统的Spring Cloud实现服务治理的主流玩法是这样的：</p>
<ul>
<li>Eureka / Consul 实现服务注册和发现</li>
<li>Zuul / Spring Cloud Gateway 等组件控制流量入口</li>
<li>Ribbon / Feign 实现服务端/客户端的负载均衡</li>
<li>Sleuth + Zipkin 实现分布式链路追踪</li>
<li>Hystrix + Turbine 实现熔断降级以及相关监控</li>
<li>Spring Boot Actuator + Spring Boot Admin 实现应用性能监控</li>
</ul>
<p>抛开整套解决方案的复杂度和学习成本，除了网关这一层对应用完全透明，其它的组件<strong>全部都是需要在业务组件上添加依赖、配置、注解的</strong>，导致每个应用服务叠Jar包叠的像一个<strong>千层饼</strong>，成也AOP，败也AOP。</p>
<p>这就像是<strong>在一张写了字的纸上，在上面划线必然影响纸上原有的内容</strong>，SpringCloud试图通过<strong>依赖注入避开业务去&quot;划线&quot;（服务治理）</strong>，但这种做法局限在&quot;纸&quot;这个二维平面上，而Service Mesh则像是在三维空间上，<strong>用垂直与整个纸面的Z轴线，贯穿整张纸</strong>, 却只会留下一个点，不会影响纸上写的任何内容。就好比二维空间找不到垂直于两条非平行线的第三条线，而在三维空间就可以轻易找到这样的线。这就是Service Mesh<strong>正交地服务治理</strong>，从<strong>独立于业务的部署架构和运维配置的维度</strong>来思考服务治理，这也是istio与Spring Cloud的<strong>本质区别</strong>，因此说istio对Spring Cloud是一个&quot;降维打击&quot;。</p>
<h3 id="异构-vs-同构" tabindex="-1">异构 vs 同构 <a class="header-anchor" href="#异构-vs-同构" aria-label="Permalink to &quot;异构 vs 同构&quot;">&ZeroWidthSpace;</a></h3>
<p>从某种意义上说，istio是一个<strong>异构微服务治理平台</strong>，相比之下，Spring Cloud更像是一个<strong>同构的微服务框架</strong>。因为istio不关心业务服务到底用的是什么技术，而Spring Cloud虽然支持其它语言，但只有全链路使用Spring生态的组件效果才能最大化。同构与异构没有谁比谁更好，取决于业务场景，不过个人认为，在开发团队的个人能力足够的情况下，选择异构化的技术栈，对团队成长，业务创新，迭代速度都很有利。</p>
<ul>
<li>同构系统的学习成本相对更低，因为开发团队都用相似的技术栈</li>
<li>异构更加灵活，根据不同的业务选择最合适的语言和技术框架</li>
<li>人力成本，同构系统大部分情况下比异构系统更低，但某些场景下，选择更合适的技术会成倍降低人力成本</li>
<li>后期维护，一般同构系统也会比异构系统更容易，人员更迭交接相对简单</li>
</ul>
<h3 id="servicemesh的前景" tabindex="-1">ServiceMesh的前景 <a class="header-anchor" href="#servicemesh的前景" aria-label="Permalink to &quot;ServiceMesh的前景&quot;">&ZeroWidthSpace;</a></h3>
<p>Service Mesh是一个很新的理念，其主流实现istio是通过<strong>自动注入SideCar</strong>和一系列外部组件实现对业务<strong>完全透明的服务治理</strong>，思路非常新颖，但目前这个技术还不够成熟，也有关于<strong>性能和复杂度</strong>方面的质疑的声音。在2018年CNCF的年度调查中，大部分企业只是对它保持关注，愿意小范围尝试，很少有企业大规模使用在生产环境中。所有技术都有一个成熟度模型，就像军事上常说的”<strong>装备一代,设计一代,预研一代</strong>“，笔者个人非常看好Service Mesh的前景，即使目前还达不到“装备一代”的成熟度，但就像Kubernetes发展了几年后，在2018年迎来了爆发一样，相信Service Mesh是未来的趋势，将会逐渐发展成行业标准。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] Linux操作系统层的故障注入]]></title>
            <link>https://code2life.top/blog/0035-fault-injection</link>
            <guid>https://code2life.top/blog/0035-fault-injection</guid>
            <pubDate>Thu, 02 May 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-linux操作系统层的故障注入" tabindex="-1">[DevOps] Linux操作系统层的故障注入 <a class="header-anchor" href="#devops-linux操作系统层的故障注入" aria-label="Permalink to &quot;[DevOps] Linux操作系统层的故障注入&quot;">&ZeroWidthSpace;</a></h1>
<p>某些情况下，为了特殊的测试场景和鲁棒性测试，我们需要模拟各种软硬件故障，尤其是网络故障注入测试。除了<strong>拔网线，断电源，删文件</strong>以外，本文讲解Linux下一些简单的网络故障注入命令，建议配合<strong>Wireshark</strong>抓包以及常用的性能监控分析工具一同食用更佳。</p>
<h2 id="进程故障注入" tabindex="-1">进程故障注入 <a class="header-anchor" href="#进程故障注入" aria-label="Permalink to &quot;进程故障注入&quot;">&ZeroWidthSpace;</a></h2>
<p>kill 命令用于给进程发送特定的信号，其中包括了进程关闭挂起等信号来制造正常或异常的进程退出，可以用&quot;kill -l&quot;查看所有可以发送的信号。关闭或挂起进程的方法很简单，如果有父-子进程的话，可以试试分别kill。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 正常的SIGTERM杀进程</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -TERM</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pid</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 强制关闭SIGKILL杀进程</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -9</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pid</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># SIGSTOP用来挂起进程， SIGCONT恢复进程运行</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -STOP</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pid</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -CONT</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pid</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>容器环境下进程相当于整个容器，干掉进程的方法更多一点，以docker为例：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># rm子命令对于正在运行的容器，-f参数等价于 SIGKILL强杀</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">container-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># stop子命令，SIGTERM信号发过去，默认10秒SIGKILL强杀</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">container-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># exec进入容器交互式Shell，执行kill，花式杀进程</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> exec</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -it</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">container-i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /bin/bash</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 进入后杀掉1号进程（通常docker内运行的应用主进程ID为1）</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 或者</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -9</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 还有一种方法是在宿主机ps找到对应的docker-containerd-shim进程，在宿主机直接执行kill</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>Kubernetes下杀进程/容器的方法与docker类似，除了容器级别操作以外，还可以通过kubectl告诉Kubernetes API Server哪个Pod需要关。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 给Pod内所有容器发送SIGTERM关闭，超过GracePeriod发送SIGKILL</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pod-nam</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 直接给Pod所有容器发送SIGKILL强关，没有商量的余地</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pod-nam</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --grace-period=0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --force</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h2 id="使用iptables注入网络故障" tabindex="-1">使用iptables注入网络故障 <a class="header-anchor" href="#使用iptables注入网络故障" aria-label="Permalink to &quot;使用iptables注入网络故障&quot;">&ZeroWidthSpace;</a></h2>
<p>iptables是一个命令行工具，用于控制Linux内核的<strong>IP包过滤系统Netfilter</strong>。iptables的配置是运维必备技能，网上有太多资料了，这里不深入讲解，只列出几个关于网络故障注入的配置命令，作用在默认的Filter表上，NAT表也可以实现故障注入，但没有Filter表来的直接明了。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 查看一下Filter表和NAT表的已配置规则</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptalbes</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -L</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 每个参数的含义可以用Explain Shell在线工具查看 https://explainshell.com/</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 13.57.193.230添加一个规则：目的地192.168.100.101的443端口的TCP包全部丢弃</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.100.101</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dport</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -A</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> OUTPUT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -j</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DROP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 删除上述规则</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.100.101</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dport</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> OUTPUT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -j</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DROP</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 添加一个规则：目的地192.168.100.101的443端口的TCP包有10%的概率丢弃</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.100.101</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -A</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> OUTPUT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dport</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> statistic</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --mode</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> random</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --probability</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -j</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DROP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 删除上述规则</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.100.101</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> OUTPUT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dport</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 443</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> statistic</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --mode</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> random</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --probability</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -j</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> DROP</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 修改规则记得保存，否则重启失效</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> iptables-save</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>注意，在RHEL7或CentOS7上，有的比较&quot;完整&quot;的版本默认安装了<strong>firewalld</strong>控制iptables，可能造成直接修改iptables失效，Ubuntu也有ufw这样的防火墙软件封装了iptables。这种情况下要么关闭防火墙，自己改iptables，要么按照防火墙命令来配置，比如红帽系的firewalld的配置命令示例如下：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 允许/拒绝 443 端口的外部访问</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">firewall-cmd</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --zone=public</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --add-port=443/tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --permanent</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">firewall-cmd</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --zone=public</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --remove-port=443/tcp</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --permanent</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 立即生效</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">firewall-cmd</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --reload</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 或者关掉firewalld自己撸iptables</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 个人认为比这些iptables套层壳的防火墙软件更好用也更灵活</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stop</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> firewalld</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> disable</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> firewalld</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>还有一种情况，在Kubernetes集群中，如果<strong>kube-proxy</strong>是用iptables模式而非ipvs模式运行的话，iptables也是不能乱改的，这个时候就需要其他故障注入工具来替代了。</p>
<h2 id="使用tc命令注入网卡数据错误或异常" tabindex="-1">使用tc命令注入网卡数据错误或异常 <a class="header-anchor" href="#使用tc命令注入网卡数据错误或异常" aria-label="Permalink to &quot;使用tc命令注入网卡数据错误或异常&quot;">&ZeroWidthSpace;</a></h2>
<p>tc 即Traffic Control，用于在Linux内核级别的流量控制，是在输出接口排列时进行处理和实现的，流量控制有这几种形式：SHAPING(限制)，SCHEDULING(调度)，POLICING(策略)，DROPPING(丢弃)，大致原理如下：</p>
<ul>
<li>针对网络物理设备（如以太网卡eth0）绑定一个队列QDisc</li>
<li>在该队列上建立分类class</li>
<li>为每一分类建立一个基于路由的过滤器filter</li>
<li>最后与过滤器相配合，建立特定的路由表</li>
</ul>
<p>具体原理可以参考<a href="https://www.cnblogs.com/yxwkf/p/5424383.html" target="_blank" rel="noreferrer">这篇文章</a>，内核流量控制不仅原理比较复杂，tc 命令的用法和参数也非常多，这里我们只关注用来注入netem模块来模拟网卡异常或故障的命令参数。这有点类似于前端经常使用的Chrome开发者工具的Network面板的网速控制。</p>
<blockquote>
<p>Netem（Netemulator） 是 Linux 2.6 及以上内核版本提供的一个网络模拟功能模块。该功能模块可以用来在性能良好的局域网中，模拟出复杂的互联网传输性能，诸如低带宽、传输延迟、丢包等等情况</p>
</blockquote>
<p>下面的wlp5s0是本机的无线网卡名称，需要替换成执行机器上真实的网卡(ifconfig 或 ip addr 可以查看网卡信息)。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 首先安装相应工具： apt/yum install iproute iperf</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 查看当前网卡设备的流量排队规则（qdisc）</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ls</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 在wlp5s0网卡设备上添加一个模拟100±50ms的延迟，最后一个25%是波动相关性系数</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delay</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 100ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 50ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 25%</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 删除上面添加的流量控制规则</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delay</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 100ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 50ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 25%</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 模拟包 延迟 + 乱序</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delay</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 100ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reorder</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 25%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 50%</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delay</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 100ms</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reorder</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 25%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 50%</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 模拟1%的丢包，30%也是波动相关性系数，或者替换成"distribution normal"实现真正的正态分布，更接近真实情况下，延迟丢包等问题往往集中出现在某个时间点</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> loss</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 30%</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> loss</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 30%</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 模拟1%的包错误</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> corrupt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> corrupt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 模拟1%的包重复</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> duplicate</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> qdisc</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> wlp5s0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> netem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> duplicate</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1%</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br></div></div><h2 id="模拟系统过载" tabindex="-1">模拟系统过载 <a class="header-anchor" href="#模拟系统过载" aria-label="Permalink to &quot;模拟系统过载&quot;">&ZeroWidthSpace;</a></h2>
<p>除了直接故障注入，还有一个间接注入的办法，就是压垮操作系统。 <a href="https://www.tecmint.com/linux-cpu-load-stress-test-with-stress-ng-tool/" target="_blank" rel="noreferrer">Stress/Stress-NG</a>是Linux下两个常用的系统级压力测试工具，<strong>stress</strong>命令简单易用，<strong>stress-ng</strong>是stress的升级版，支持<strong>数百个参数定制各种压CPU、内存、IO、网络</strong>的姿势。在系统过载的场景下，应用服务可能会出现意想不到的错误或异常，在测试<strong>负载均衡和熔断降级</strong>时非常有用。这里只列举了几个常用的命令，详细使用参考&quot;stress-ng --help&quot;或&quot;man stress-ng&quot;。另外，这些“烤机”命令来测试服务器性能也是不错的。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 一般默认源就有这两个工具，直接安装</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apt/yum</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stress</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stress-ng</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 在两个CPU核心上跑开方运算，并且启动一个不断分配释放1G内存的线程，运行10秒后停止</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">stress</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --cpu</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --vm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --vm-bytes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1G</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --timeout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 启动一个线程不断执行sync系统调用回写磁盘缓存，并且启动一个线程不停地写入删除512MB数据，运行10秒停止</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">stress</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --io</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --hdd</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --hdd-bytes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 512M</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --timeout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># stress-ng 基本用法与stress完全兼容，但有更多的参数可选，并且可以查看统计信息</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># --sock 可以模拟大量的socket连接断开以及数据的发送接收等等</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">stress-ng</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --sock</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --timeout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --metrics-brief</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] 打通Kubernetes内网和局域网的N种方法]]></title>
            <link>https://code2life.top/blog/0037-k8s-tunnel</link>
            <guid>https://code2life.top/blog/0037-k8s-tunnel</guid>
            <pubDate>Mon, 15 Apr 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-打通kubernetes内网和局域网的n种方法" tabindex="-1">[DevOps] 打通Kubernetes内网和局域网的N种方法 <a class="header-anchor" href="#devops-打通kubernetes内网和局域网的n种方法" aria-label="Permalink to &quot;[DevOps] 打通Kubernetes内网和局域网的N种方法&quot;">&ZeroWidthSpace;</a></h1>
<p>由于Kubernetes集群会使用CNI插件创建Pod/Service内部子网，外面一般无法访问内部IP和域名，给开发、测试、 联调带来了很大的麻烦，因此打通开发测试环境Kubernetes集群内部子网和办公室的局域网、实现互联互通是经常遇到的问题。</p>
<p>本篇介绍<strong>四种</strong>解决方案以及其<strong>适用场景和优缺点</strong>，这四种方案分别利用了OSI网络模型的不同层级实现。</p>
<p><img src="//filecdn.code2life.top/k8s-net-osi.png" alt=""></p>
<ul>
<li>方案一：在L7 <strong>应用层</strong>，使用<strong>kubectl自带的子命令</strong>（proxy port-forward）打通部分服务；</li>
<li>方案二：在L3 <strong>网络层</strong>，使用<strong>自定义路由</strong>无缝打通Kubernetes集群内网；</li>
<li>方案三：在L4/L5+ <strong>传输/应用层</strong>，使用<strong>Socks5代理</strong>基本打通Kubernetes集群内网；</li>
<li>方案四：在L2/L3 <strong>数据链路/网络层</strong>，实用Kubernetes集群内搭建的<strong>VPN服务器</strong>实现无缝打通集群内网。</li>
</ul>
<h2 id="方案一-kubernetes的proxy和forward" tabindex="-1">方案一：Kubernetes的Proxy和Forward <a class="header-anchor" href="#方案一-kubernetes的proxy和forward" aria-label="Permalink to &quot;方案一：Kubernetes的Proxy和Forward&quot;">&ZeroWidthSpace;</a></h2>
<p>Kubernetes本身提供了基于API Server的Proxy和Port Forward机制。比如Proxy子命令：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 执行Proxy命令，或直接使用API Server的地址访问类似下面的URL</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> proxy</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --port</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8001</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 运行后即可在本地直接访问到Kubernetes API Server</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># API Server提供了Proxy接口来访问到内部的Service</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">https://localhost:8001/api/v1/namespaces/$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">{ns}</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/services</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/${</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">schema}:$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">{service_name}</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">${port}</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/proxy/</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>Proxy一般用的不多，因为需要Token才能调用API Server的接口，不能比较透明地打通某个服务。</p>
<p>常用的一般是Port Forward，在本地监听某个端口，本地流量通过网络隧道到达某个Pod的端口，实现访问localhost:xx等价于在访问集群内网的某个Pod/Service。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 冒号前面的是本地监听的Port，后面是Pod/Service声明的Port</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 另外，K9S、Kube-Forwarder 等工具可以更方便的操作，无需执行完整命令</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port-forward</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pods/some-pod-name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 8080:8080</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> some-namespace</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>这两种方式的<strong>原理</strong>都是在第七层，利用kubectl建立与API Server的<strong>WebSocket连接</strong>，作为网络隧道部分打通内部服务，而不是底层和局域网实现网络互通，因此IP和端口会变成localhost的，集群内DNS也无法解析（除非自定义hosts）。</p>
<p><strong>适用场景</strong>：</p>
<ul>
<li>联调时调用Kubernetes内<strong>个别</strong>的HTTP服务、简单的TCP服务，以及<strong>无需查找集群内DNS</strong>的场景。</li>
</ul>
<p><strong>优点：</strong></p>
<ul>
<li>Kubernetes原生解决方案，开箱即用，只需执行kubectl命令即可，也有可视化工具可以做；</li>
<li>Port Forward具体哪些Pod的权限可通过RBAC机制控制，比较安全。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>需要每个使用者都执行命令，而且每个组件都要Forward一下，不方便；</li>
<li>Pod发生改变时需要重新执行kubectl来Forward；</li>
<li>对于一些需要打通Kubernetes DNS的TCP服务，或者直连同样的内网IP的场景不适用（Redis Cluster， Kafka，MongoDB等等，比如：Forward集群的MongoDB到本地，本地连接到localhost:27017是MongoDB的Primary节点，服务端返回了一个 mongo-secondary.mongo.cluster.local 的域名让客户端去连，客户端就懵逼了）。</li>
</ul>
<h2 id="方案二-通过路由跳转打通容器网络" tabindex="-1">方案二：通过路由跳转打通容器网络 <a class="header-anchor" href="#方案二-通过路由跳转打通容器网络" aria-label="Permalink to &quot;方案二：通过路由跳转打通容器网络&quot;">&ZeroWidthSpace;</a></h2>
<p>如果Kubernetes集群就部署在局域网内或者部署在自己的数据中心，整个链路上的<strong>网关可配</strong>的话，用<strong>静态路由表</strong>是最简单的办法，其原理是作用在网络模型的第三层 <strong>网络层</strong>，直接告诉网关某些IP要发给谁。</p>
<p>举一个最简单的例子，某开发环境的Kubernetes部署在和办公室同一个局域网，有下面两条线路可以打通网络：</p>
<p><img src="//filecdn.code2life.top/k8s-tunnel-static-route.png" alt=""></p>
<p>此时在网关路由器上添加<strong>静态路由规则</strong>，把属于Kubernetes的<strong>Pod/Service CIDR</strong>的IP包全转给其中某个Kubernetes节点，这样访问10.96.0.1这样的IP，网络包会到达某个集群物理节点，而集群内的物理节点或VM，一般K8S网络插件(CNI)都会做与Pod/Service CIDR的互通。</p>
<p>如果Kubernetes和本地机器处于<strong>同一个网关</strong>下，甚至仅在<strong>本地机器上</strong>添加一条静态路由到路由表即可（即图中下面的那个箭头，直接可以实现与Kubernetes内网的互通）：</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 命令中下面两处需要替换成真实的值</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 集群子网： 10.96.0.0  /12 or 255.240.0.0</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 集群某节点： 192.168.1.20</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Windows </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ADD</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.96.0.0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> MASK</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255.240.0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.1.20</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Linux</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ip</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 10.96.0.0/12</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> via</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.1.20</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eth0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># MacOS</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> route</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -net</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10.96.0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -netmask</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255.240.0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 192.168.1.20</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如果在网关上（路由器/交换机）统一配置时，各个厂商设备的命令有所不同</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>如果Kubernetes部署的机器和公司办公室<strong>不在同一个网关下</strong>，或者部署在自建数据中心的，整个链路会多几个网关，链路上<strong>每个网关</strong>都需要配置路由表路由相应的CIDR到相邻的跃点，类似下图：</p>
<p><img src="//filecdn.code2life.top/k8s-static-route2.png" alt=""></p>
<p>有了第三层的路由支持，<strong>DNS的问题</strong>也迎刃而解了，直接把Kubernetes的DNS Server（如CoreDNS），当作本地DNS服务器，或者插入到DNS查找链中作为一个Forwarder即可。这样整个局域网已经与Kubernetes内网完全互联互通了。</p>
<p><strong>适用场景：</strong></p>
<ul>
<li>网关配置可以修改，比如都在公司局域网内，或集群所在数据中心的网关路由器是可修改的；</li>
<li>内部的开发环境Kubernetes，不担心敏感数据等安全问题。</li>
</ul>
<p><strong>优点：</strong></p>
<ul>
<li>方便，无需部署任何组件，仅在一个或多个网关上做静态路由配置即可，透明、高效；</li>
<li>对开发测试人员几乎完全透明，不需要任何额外操作。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>需要负责网络的IT人员额外配置；</li>
<li>Pod/Service网段不能和本地局域网的网段有冲突，多个Kubernetes集群之间也不能有CIDR冲突，否则不好配置路由表；</li>
<li>除了一些局域网或自建DC中的Kubernetes，大部分情况下可能用的是<strong>云服务</strong>，我们没办法修改云服务商的路由表，此方案很难实现，就要用方案三和四了。</li>
</ul>
<h2 id="方案三-通过shadowsocks打通容器网络" tabindex="-1">方案三：通过Shadowsocks打通容器网络 <a class="header-anchor" href="#方案三-通过shadowsocks打通容器网络" aria-label="Permalink to &quot;方案三：通过Shadowsocks打通容器网络&quot;">&ZeroWidthSpace;</a></h2>
<p>方案三、四的原理类似：通过在Kubernetes内网搭建一个&quot;间谍&quot;服务，客户端连到这个服务建立一个虚拟的隧道，让局域网的部分网络流量通过这个专属的隧道打到Kubernetes内网中，感觉就像在Kubernetes内网一样。方案三的Shadowsocks与方案四的VPN两个方案不同的地方在于，前者在L4/L5而后者一般作用在L2/L3。</p>
<p>Shadowsock Server的方案大致原理如图所示：</p>
<p><img src="//filecdn.code2life.top/k8s-tunnel-ss.png" alt=""></p>
<ul>
<li>首先本机通过RFC 1928定义的标准的<a href="https://www.ietf.org/rfc/rfc1928.txt" target="_blank" rel="noreferrer">Socks5协议</a>代理TCP连接的流量；</li>
<li>然后SS Client和SS Server通过加密通信把流量丢给SS Server；</li>
<li>SS Server最终发送这些TCP包到目标机器，再原路返回回去，通过两重转发实现“代理”的目的。</li>
</ul>
<p>在Kubernetes中部署Shadowsocks Server很简单，直接<strong>Apply下面的Yaml</strong>即可。如果不想每个客户端都安装一个Shadowsocks Client，也能让其中某台客户端开启Socks5<strong>允许局域网连接</strong>，让其他局域网机器都连过来。</p>
<div class="language-yaml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">apps/v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Deployment</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">shadowsocks-deployment</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ssserver</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  replicas</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  selector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    matchLabels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ssserver</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      labels</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ssserver</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      containers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ssserver-container</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">shadowsocks/shadowsocks-libev:v3.2.0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        command</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">          # need to modify "-d" to k8s core-dns</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">          "ss-server"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-p"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"8388"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-t"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"300"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-k"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"your-password"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-m"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"aes-256-cfb"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"--fast-open"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-d"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"10.96.0.10,8.8.8.8"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"-u"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ]</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">        ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">containerPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">TCP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">tcp</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">containerPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">UDP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">          name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">udp</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      nodeSelector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "some-condition"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">apiVersion</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v1</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Service</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">metadata</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">socks-proxy-svc</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  namespace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">default</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">NodePort</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    targetPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    nodePort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">32088</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">TCP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">tcp</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">port</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    targetPort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8388</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    nodePort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">32088</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    protocol</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">UDP</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">udp</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  selector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ssserver</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br></div></div><p>我们再看这个方案的<strong>DNS问题</strong>，由于TCP流量被代理到内网中，才真正发送到目标机器的，因此对于本机的应用程序来说<strong>压根就不用提前自己发UDP包做DNS解析</strong>了，<strong>一股脑把流量塞给Sock5代理</strong>即可。</p>
<p>但还有一个问题，怎么确保我们要联调测试的应用程序，流量一定会走Sock5代理呢？</p>
<p>我们在运行Shadowsocks Client时，开启系统代理时它会帮我们做一件事情，就是给<strong>操作系统自动设置Sock5代理</strong>，像下面这样的:</p>
<p><img src="//filecdn.code2life.top/ss-win-proxy.png" alt=""></p>
<p>浏览器和一些应用软件，也可以读取操作系统的设置作为默认值，但仍然<strong>不能确保</strong>所有进程的TCP流量，都走Sock5代理。</p>
<p>比如Java进程的<strong>JVM启动参数</strong>如果不加：<strong>-DsocksProxyHost和-DsocksProxyPort</strong> 两个参数，并不一定会走Socks5代理。这时有个更彻底的办法，用Proxifier<strong>强制某些进程</strong>的流量全部走Sock5代理，传送门：<a href="https://www.proxifier.com/download/" target="_blank" rel="noreferrer">https://www.proxifier.com/download/</a>。Proxifier是个非常好用的工具，在更底层拦截了网络流量转到代理服务器中，这里就不扯远了。</p>
<p><strong>适用场景：</strong></p>
<ul>
<li>想<strong>几乎</strong>无缝透明的访问集群的任何内部Pod/Service IP和Domain，定向透传到Kubernetes集群内的流量；</li>
<li>Kubernetes集群在云服务器上，有公网IP可以当作SS Server服务器IP。</li>
</ul>
<p><strong>优点：</strong></p>
<ul>
<li>服务端方案比较轻量，维护相对简单；</li>
<li>代理开关方便，客户端比较灵活；</li>
<li>按需代理，不影响大多数网络流量，即使有瓶颈扩容也很方便。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>客户端初次使用可能稍微有些麻烦；</li>
<li>虽然浏览器或程序用代理能使用集群内部DNS，但即使开了代理，本地直接nslookup解析Kubernetes内部域名也不通，<strong>DNS问题是间接解决的</strong>。</li>
</ul>
<h2 id="方案四-通过vpn打通容器网络" tabindex="-1">方案四：通过VPN打通容器网络 <a class="header-anchor" href="#方案四-通过vpn打通容器网络" aria-label="Permalink to &quot;方案四：通过VPN打通容器网络&quot;">&ZeroWidthSpace;</a></h2>
<p>VPN是在远程办公场景时常用的方案，借用VPN的思路打通Kubernetes内网也可以实现。常用的VPN有两类，作用于网络模型的L2或L3：</p>
<ul>
<li>L2TP（Layer Two Tunneling Protocol）：主要是作用于第二层，支持非IP网络，开销稍高，搭配第三层的IPSec协议可以做隧道验证，密钥协商和流量的加密；</li>
<li>PPTP（Point to Point Tunneling Protocol）：点对点隧道协议， 使用第三层的GRE（Generic Routing Eneapsulation）建立隧道，以及第二层的点对点协议传输数据。</li>
</ul>
<p>在Kubernetes环境中部署VPN服务器实现办公室局域网到Pod/Service网络的互通，网络上也有一些教程。有个Github项目是在容器环境下搭建IPSec VPN的：<a href="https://github.com/hwdsl2/docker-ipsec-vpn-server" target="_blank" rel="noreferrer">https://github.com/hwdsl2/docker-ipsec-vpn-server</a>，把运行容器命令改成Apply Deployment/StatefulSet Yaml，或者以在某个Kubernetes节点上以Host Network的模式运行容器，也相当于在Kubernetes集群中放了一个“间谍”，搭好服务端之后，客户端用操作系统自带的VPN工具连进去即可。</p>
<p><strong>适用场景：</strong></p>
<ul>
<li>需要完全<strong>无缝透明地</strong>访问Kubernetes的Pod/Service内部网络。</li>
</ul>
<p><strong>优点：</strong></p>
<ul>
<li>操作系统自带VPN功能，客户端比较简单，只需要开关VPN即可实现透明的网络互通了；</li>
<li>作用于网络模型更底层，能实现完全透传。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>VPN打开会影响所有网络流量，导致大量不需要走Kubernetes的流量走到集群内了；</li>
<li>VPN的实现相对比较重，效率可能不如其他方案。</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文讲解了四种打通Kubernetes Pod/Service内部子网的方案，但上述各个方案都有一个前提：打通的是<strong>开发测试环境的</strong>Kubernetes集群。</p>
<p>在<strong>生产环境</strong>上做这些事情是<strong>非常危险</strong>的，即使是原生的Port Forward也要严格<strong>控制权限</strong>，任何一个后门都可能被黑客当做突破口<strong>一锅端</strong>。因此做之前一定要考虑安全方面的影响，开发测试环境也同样不能大意，尽量避免暴露内部的东西到公网。</p>
<p>我个人推荐的各个方案<strong>使用优先级</strong>是和本文顺序一致的：</p>
<ul>
<li>能用方案一Port Forward解决的就不要看下面三种方案了，Forward虽然不能做到透传，但相对更安全可靠；</li>
<li>能有条件配置方案二的路由表的，也不用考虑方案三和四了，静态路由过去、DNS解析链条配置好，对使用端几乎完全透明，也没有任何性能损耗；</li>
<li>方案三和四的思路差不多，在集群内部放一个“间谍”（SS Server 、 VPN Server），网络流量从代理服务器发出去。VPN的管理复杂一些，没有IP和域名级别细粒度的流量控制，可能导致无关流量都涌进来成为瓶颈；Socks代理对使用端稍微麻烦一些，不是完全的透传，但效率和灵活度更高，方案三和四<strong>按需选择</strong>吧。</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[DevOps] 3分钟给Nginx开启HTTPS与HTTP2]]></title>
            <link>https://code2life.top/blog/0034-lets-encrypt</link>
            <guid>https://code2life.top/blog/0034-lets-encrypt</guid>
            <pubDate>Mon, 11 Mar 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops-3分钟给nginx开启https与http2" tabindex="-1">[DevOps] 3分钟给Nginx开启HTTPS与HTTP2 <a class="header-anchor" href="#devops-3分钟给nginx开启https与http2" aria-label="Permalink to &quot;[DevOps] 3分钟给Nginx开启HTTPS与HTTP2&quot;">&ZeroWidthSpace;</a></h1>
<p>由于种种不可抗力以及晚期懒癌的影响，很久没更新博客了，这篇也偷个懒写一篇没什么营养，但很实用的小技巧</p>
<h2 id="let-s-encrypt" tabindex="-1">Let's Encrypt <a class="header-anchor" href="#let-s-encrypt" aria-label="Permalink to &quot;Let's Encrypt&quot;">&ZeroWidthSpace;</a></h2>
<p>HTTPS + HTTP2 已经是大势所趋，对于个人用户，<a href="https://www.cnblogs.com/sslwork/p/6193256.html" target="_blank" rel="noreferrer">OV、EV</a>的HTTPS证书无法获取，通常用免费的DV证书来实现HTTPS，其中<a href="https://letsencrypt.org/" target="_blank" rel="noreferrer">Let's Encrpyt</a>的证书是最易用的，具体流程如下：</p>
<h3 id="安装certbot并创建证书" tabindex="-1">安装CertBot并创建证书 <a class="header-anchor" href="#安装certbot并创建证书" aria-label="Permalink to &quot;安装CertBot并创建证书&quot;">&ZeroWidthSpace;</a></h3>
<p><a href="https://certbot.eff.org/" target="_blank" rel="noreferrer">CertBot</a>提供了自动化的方式管理HTTPS证书，但通过实践发现太过自动化反而失去了灵活性，比如我的Nginx部署在容器环境中，宿主机并没有Nginx，就需要手动生成证书了,假设域名为domain.com，下面以CentOS为例生成证书。</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">yum</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> certbot</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 使用这条命令之前，首先在云服务商后台，将域名解析到执行这条命令的机器IP</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 这里 /webroot-path 需要替换，每个子域名都需要添加一个 -d， 下面再讲解这条命令详细含义</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">certbot</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> certonly</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --webroot</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /webroot-path</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> domain.com</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  www.domain.com</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>certonly模式需要指定一个webroot, 这个目录用于certbot写入域名验证信息，让certbot知道通过这些域名，确实能访问到这台机器，并且访问到的内容与预期一致，因此，这个webroot目录顾名思义，就是静态资源服务器的资源目录。</p>
<p>certbot会在里面写入 <strong>./.well-known/acme-challenge/</strong> 用于验证，验证完了就删除，webroot参数填写资源目录路径即可，比如：</p>
<ul>
<li>Nginx的 root 配置路径，如 /usr/share/nginx/html</li>
<li>Java应用的webapp、static resource 资源目录， nodejs的 static 中间件对应目录等等</li>
</ul>
<p>某些云服务商提供的TLS证书服务通常是用DNS验证，比如添加一条TXT记录等等，certbot也有多种方式，但感觉最简单的办法就是 &quot;certbot certonly&quot;,正确的输出包含&quot;Congratulations&quot;即通过验证，证书会放在 <strong>/etc/letsencrypt</strong>下面，<strong>/etc/letsencrypt/live</strong>文件夹中存放的最新证书的软链接。</p>
<h3 id="证书自动续期" tabindex="-1">证书自动续期 <a class="header-anchor" href="#证书自动续期" aria-label="Permalink to &quot;证书自动续期&quot;">&ZeroWidthSpace;</a></h3>
<p>Lets Encrypt的证书有效时间只有90天，因此certbot提供了renew命令用于自动续期，添加crontab即可一劳永逸</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># crontab -e, 然后插入下面的命令，每周一两点自动检查续期</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">00</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /usr/bin/certbot</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> renew</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --webroot</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /webroot-path</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> >></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /var/log/le-renew.log</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>不放心可以先 dry run 看下是否正常，看到下面的输出就是正常的</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/usr/bin/certbot</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> renew</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dry-run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --webroot</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -w</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /webroot-path</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#** DRY RUN: simulating 'certbot renew' close to cert expiry</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#**          (The test certificates below have not been saved.)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#Congratulations, all renewals succeeded. The following certs have been #renewed:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#  /etc/letsencrypt/live/domain.com/fullchain.pem (success)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># ......</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h2 id="nginx配置证书及http2" tabindex="-1">Nginx配置证书及Http2 <a class="header-anchor" href="#nginx配置证书及http2" aria-label="Permalink to &quot;Nginx配置证书及Http2&quot;">&ZeroWidthSpace;</a></h2>
<p>上面通过certbot生成免费证书，配置到Nginx的443 Server模块即可，至于Http2，网上有很多教程还是添加Nginx模块来搞Http2，但Nginx在1.9.5之后就原生支持Http2了，仅需几行即可开启Http2，新版本的OpenSSL也支持TLS1.3</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span># 假设域名为domian.com</span></span>
<span class="line"><span>server {</span></span>
<span class="line"><span>    listen 443 ssl http2;</span></span>
<span class="line"><span>    listen [::]:443 ssl http2;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    server_name domain.com;</span></span>
<span class="line"><span>    root /usr/share/nginx/html;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;</span></span>
<span class="line"><span>    ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;</span></span>
<span class="line"><span>    ssl_trusted_certificate /etc/letsencrypt/live/domain.com/chain.pem;</span></span>
<span class="line"><span>    </span></span>
<span class="line"><span>    ssl_prefer_server_ciphers on;</span></span>
<span class="line"><span>    ssl_session_cache shared:SSL:10m;</span></span>
<span class="line"><span>    ssl_session_timeout 10m;</span></span>
<span class="line"><span>    ssl_protocols TLSv1.2 TLSv1.3;</span></span>
<span class="line"><span>    ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>添加好HTTPS+HTTP2的Server模块后，nginx -t 验证没问题就可以重启Nginx或reload配置了，如果需要禁用HTTP，在80端口的Server模块添加一个rewrite即可,并添加一个HSTS的header防止SSL剥离攻击：</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";</span></span>
<span class="line"><span>location / {</span></span>
<span class="line"><span>    rewrite ^ https://$http_host$request_uri? permanent;</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[分布式专题] Kubernetes及Helm术语和常用命令]]></title>
            <link>https://code2life.top/blog/0032-k8s-commands</link>
            <guid>https://code2life.top/blog/0032-k8s-commands</guid>
            <pubDate>Sat, 27 Oct 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="分布式专题-kubernetes及helm术语和常用命令" tabindex="-1">[分布式专题] Kubernetes及Helm术语和常用命令 <a class="header-anchor" href="#分布式专题-kubernetes及helm术语和常用命令" aria-label="Permalink to &quot;[分布式专题] Kubernetes及Helm术语和常用命令&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="kubernetes资源对象及管理命令" tabindex="-1">Kubernetes资源对象及管理命令 <a class="header-anchor" href="#kubernetes资源对象及管理命令" aria-label="Permalink to &quot;Kubernetes资源对象及管理命令&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="kubernetes常用资源对象" tabindex="-1">Kubernetes常用资源对象 <a class="header-anchor" href="#kubernetes常用资源对象" aria-label="Permalink to &quot;Kubernetes常用资源对象&quot;">&ZeroWidthSpace;</a></h4>
<p>在汇总Kubernetes常用命令之前, 需要先理解常用的资源对象, 因为Kubernetes集群的管理是声明式的, 大部分命令都是 get / create / apply / edit / delete 加上资源的名称或定义. 这里有<a href="https://www.cnblogs.com/zhenyuyaodidiao/p/6500720.html" target="_blank" rel="noreferrer">一篇详细一些的文章</a>. 如果没有接触过Kubernetes, 可以参考笔者以前分享的一个<a href="//k8s-share.code2life.top/#/" target="_blank" rel="noreferrer">PPT</a>大概了解一下.</p>
<p>常见的资源对象:</p>
<ul>
<li><strong>Node</strong> 节点, 每个物理机/虚拟机就是一个节点</li>
<li><strong>Namespace</strong> 命名空间, 大部分资源对象都属于特定的命名空间, 用于隔离不同类型或不同环境的资源</li>
<li><strong>ConfigMap / Secret</strong> 明文/密文常量, 集群的配置中心, 可以用于把配置挂载到容器的<strong>文件路径</strong>或<strong>环境变量</strong>中</li>
<li><strong>Pod</strong> 容器组 翻译过来是豆荚, 里面的豆子就是容器(container)
<ul>
<li>Pod就是指一组<strong>共享文件/进程上下文的容器集合</strong>, 也是K8S管理的基本单元</li>
<li>一个Pod一般表示一个应用的实例, 一般不会直接创建Pod, Deployment/StatefulSet/Job/DaemonSet等等都会创建Pod</li>
</ul>
</li>
<li><strong>Service / EndPoint</strong> 服务和服务端点
<ul>
<li>Service是对一个应用对外提供服务的抽象, 每个服务对应的地址就是端点(Endpoint)</li>
<li>Service会对多个Endpoint做<strong>负载均衡</strong></li>
<li>大部分情况下Endpoint地址的就是Pod的IP+端口</li>
<li>如果Service没有selector, 这时Service就是一个<strong>Headless Service</strong>, 可以手动创建Endpoint指向外部服务</li>
</ul>
</li>
<li><strong>Ingress</strong> 入口, 相当于集群的<strong>网关</strong>
<ul>
<li>Ingress是一个抽象的概念, 具体是实现一般是Ingress Controller部署在集群的边缘节点上</li>
<li>Ingress Controller有很多选择, 比如K8S官方推荐的traefik, 以及用的比较多的Nginx IngressController</li>
<li>有了Ingress Controller, 就可以声明很多Ingress配置来路由集群的访问规则</li>
<li>Ingress可以配置通过URL Path, Hostname路由, 甚至通过Annotation改写URL<a href="https://docs.giantswarm.io/guides/advanced-ingress-configuration/" target="_blank" rel="noreferrer">等等</a></li>
</ul>
</li>
<li><strong>StatefulSet</strong> 有状态集合, 不能随意调度或水平扩展的有状态应用
<ul>
<li>大部分服务应该设计为无状态的, 把状态集中到某个外部应用, 比如Redis</li>
<li>最典型的StatefulSet有Redis, MySQL, Consul, Zookeeper等</li>
</ul>
</li>
<li><strong>Deployment</strong> 表示一个无状态应用的部署
<ul>
<li>Deployment会生成一个RelicaSet控制Pod的数量和生命周期, 提供应用的蓝绿部署/滚动更新的能力</li>
<li>Deployment配置很多, 能够实现大部分运维需求, 比如保留多个revision指定回滚版本等等</li>
</ul>
</li>
<li><strong>DaemonSet</strong> 守护进程集, 表示需要在<strong>每个节点上都运行</strong>的Pod, 典型的应用有
<ul>
<li>CNI的实现比如Flannel网络插件</li>
<li>日志收集Agent比如Filebeat</li>
<li>监控收集Agent比如Prometheus NodeExporter</li>
</ul>
</li>
<li><strong>ReplicaSet</strong> 副本集
<ul>
<li>Deployment的核心, 声明式的控制Pod的数量, 一般由Deployment生成无需手工创建</li>
</ul>
</li>
<li><strong>ReplicaController</strong> 复制控制器, 初始版本的Deployment, 目前基本已经被废弃了</li>
<li><strong>Volume</strong> 存储卷, 每个Pod 都可以在模板中声明 Volume 资源, 作为<strong>Pod的存储源</strong>
<ul>
<li>Pod在拥有Volume之后, 决定把这个存储资源挂载到容器的哪个目录中, 需要配置<strong>VolumeMount</strong></li>
<li>Volume有很多类型, 比如默认的emptyDir, 或者ConfigMap, 以及常用的PersistentVolumeClaim</li>
</ul>
</li>
<li><strong>PersistentVolume</strong> 持久卷, 简称PV. 是<strong>集群的存储资源</strong>
<ul>
<li>就像Node是集群统一管理的计算资源, PersistentVolume就是集群统一管理分配的存储资源</li>
<li>PV也有很多类型, 比如NFS, 开源文件系统GlusterFS, 云存储提供商的网络磁盘比如EBS等</li>
</ul>
</li>
<li><strong>PersistentVolumeClaim</strong> 持久卷声明, 简称PVC, 是Volume常见的一种类型
<ul>
<li>PVC指的是Pod向集群索要存储资源(PV)的一个订单, 比如XXX Pod需要 10G 存储, 读写模式是什么, 这个声明就是一个PVC</li>
<li>PVC声明成功绑定PV之后, Pod向PV中读写文件, Pod销毁PV也会回收掉</li>
</ul>
</li>
<li><strong>StorageClass</strong> 存储类, 用于<strong>动态创建PersistentVolume</strong>的存储管理器
<ul>
<li>StorageClass像是一个工厂, Pod需要存储资源找StorageClass要, StorageClass就去动态创建一个PV出来</li>
</ul>
</li>
<li><strong>HorizontalPodAutoscaler</strong> 水平扩展器, 可以通过<strong>kubectl autoscale</strong>创建出来, 用于在达到某个负载条件时自动扩容</li>
<li><strong>NetworkPolicy</strong> 网络策略, 一般很少用到
<ul>
<li>默认情况下Pod, Service子网内部都是直接能通的. NetworkPolicy就是在<strong>内部子网中的防火墙</strong></li>
<li>不是所有的CNI实现都支持NetworkPolicy, 比如Calico支持, Flannel就不支持</li>
</ul>
</li>
<li><strong>PodSecurityPolicy</strong> 用于限制Pod的权限, 比如可以挂载的卷, 能够使用的端口<a href="https://kubernetes.io/docs/concepts/policy/pod-security-policy/" target="_blank" rel="noreferrer">等等</a></li>
<li><strong>Job / Cron Job</strong> 短期任务或定时任务, 一般应用服务很少用到</li>
<li><strong>ResourceQuota / LimitRange</strong> Namespace级别的资源限制.
<ul>
<li>LimitRange控制namespace下<strong>每个Pod或Container</strong>的资源占用范围,</li>
<li>ResourceQuota控制整个namespace的资源占用<strong>总量</strong></li>
<li>这两者和<strong>Pod模板</strong>的资源限制是不同的, Pod template里面的<strong>resource定义的requests/limits</strong>只对该Pod有效</li>
</ul>
</li>
<li><strong>ServiceAccount / Role / ClusterRole / ClusterRoleBinding</strong>
<ul>
<li>集群的RBAC权限管理相关的, 待补充</li>
</ul>
</li>
</ul>
<h4 id="kubectl-常用命令" tabindex="-1">Kubectl 常用命令 <a class="header-anchor" href="#kubectl-常用命令" aria-label="Permalink to &quot;Kubectl 常用命令&quot;">&ZeroWidthSpace;</a></h4>
<p>kubectl是管理Kubernetes集群的命令行工具(cli), 很多编辑器插件都是基于kubectl来实现的, 比如VSCode Kubernetes插件. 使用之前需要先配置好 <strong>~/.kube/config</strong> 文件. Windows下则是<strong>C:/Users/xxx/.kube/config</strong>. 该文件是一个yaml文件, 存放集群的<strong>访问授权信息</strong>.</p>
<ul>
<li>config文件主要由<strong>contexts, clusters, users</strong>三个部分组成, 以及current-context声明默认的集群</li>
<li>切换集群需要用 <strong>kubectl config use-context</strong> 命令更改当前管理的集群</li>
</ul>
<p>常用运维命令合集:</p>
<ul>
<li><strong>给节点打上label</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> label</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nodes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> node1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> label=value</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>删除节点的label</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> label</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nodes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> node1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> label-</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>创建一个ServiceAccount</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serviceaccount</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kube-system</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>绑定角色</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clusterrolebinding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --clusterrole</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cluster-admin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --serviceaccount=kube-system:tiller</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>更新yml资源</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fileOrDirectory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> myns</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>删除yml资源</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> fileOrDirectory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> myns</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>获取yml资源列表</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po/rs/svc/ep/xxx</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> myns</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>获取yml详细状态</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> po/rs/svc/ep/xxx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxx</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> myns</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>获取某个ServiceAccount的Key</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kube-system</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> secret</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kube-system</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> secret</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eks-admin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> awk</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $1}'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>代理访问集群内部Service</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> proxy</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --address</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.0.0.0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --accept-hosts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '.*'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8080</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>访问地址是**/api/v1/namespaces/xxns/services/http:servicexx:/proxy/**</li>
<li>proxy的原理是通过API Server提供的<strong>Proxy API</strong>代理Service的访问, kubectl proxy默认8001端口且仅本机访问</li>
<li><strong>代理访问某个Pod的端口</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port-forward</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> websrv-elasticsearch-client-5cdd5c5589-q479x</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 9200:9200</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>访问地址是本地设备的某个端口, 比如127.0.0.1:9200</li>
<li>port-forward的原理是通过与API-Server建立SPDY连接代理Pod的访问</li>
<li><strong>回滚部署</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rollout</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> undo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> deployment</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> hello-deployment</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --to-revision=2</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>不加 <strong>to-revision</strong> 参数则回滚到部署之前的版本</li>
<li>与之对应的还有一个rolling-update命令用于滚动更新ReplicationController, 但这种方式已经弃用了, Deployment直接Apply就是滚动更新</li>
<li><strong>更改动态PV的默认StorageClass</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> patch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> storageclass</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> standard</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">metadata</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">annotations</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">storageclass.kubernetes.io/is-default-class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">true</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}}}"</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><ul>
<li><strong>创建SSL证书</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> secret</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tls</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxx-cert</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --key</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /opt/ssl/xxx.key</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --cert</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /opt/ssl/xxx.crt</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> myns</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h2 id="helm-术语以及常用命令" tabindex="-1">Helm 术语以及常用命令 <a class="header-anchor" href="#helm-术语以及常用命令" aria-label="Permalink to &quot;Helm 术语以及常用命令&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>2020年更新：Helm3.0已经发布，无需服务端Tiller，客户端命令行参数也有所变化，另外除了Helm，对于需求比较简单的场景（只需要动态参数替换功能），Kustomize也是一个不错的选择。</p>
</blockquote>
<p>Helm 核心概念:</p>
<ul>
<li>Helm 是一个<strong>Kubernetes应用的包管理工具</strong>, 管理一个应用在Kubernetes中的的部署结构, 版本控制等等</li>
<li>一个应用就是一个<strong>Chart</strong>, 包括一堆模板化的yaml定义, 以及values.yaml作为动态模板的默认参数配置</li>
<li>一次部署就是一个<strong>Release</strong>, Release就是Chart中的模板yaml传入参数替换成真正的Kubernetes中的yaml, 并且将这些yaml通过<strong>helm服务端(tiller)<strong>部署到集群中的过程, Release是有版本的, 可以</strong>回退或更新</strong></li>
<li>Chart存放的地方就是<strong>Repository</strong>, 一般是远程的<strong>HTTP文件服务器</strong>, 用来管理Chart本身的版本. 默认用的中心仓库, <a href="https://github.com/helm/charts" target="_blank" rel="noreferrer">Helm社区维护的Charts</a></li>
</ul>
<p>Helm 命令合集:</p>
<ul>
<li><strong>helm初始化</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --service-account</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>
<p>该命令是服务端+客户端初始化, 会创建Tiller镜像的Deployment, 如果仅客户端用 helm init -c</p>
</li>
<li>
<p>指定ServiceAccount需要首先创建tiller这个Account并绑定ClusterAdmin角色</p>
</li>
<li>
<p>Helm命令会读取kube config, 需要先配置好kubectl</p>
</li>
<li>
<p><strong>搜索中心仓库的Chart</strong></p>
</li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> search</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>获取或列出当前集群中部署的helm Release</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>模拟安装某个Chart用于测试</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --dry-run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --debug</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> values.yaml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>安装某个Chart</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --namespace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> infra-redis</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> values.yaml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>模板内容替换</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 语法与install类似, 输出模板替换后的yaml</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> template</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --namespace</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> infra-redis</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> values.yaml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><ul>
<li><strong>删除部署过的某个Release</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> delete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>搭建一个本地helm Repo服务器</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nohup</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serve</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --address</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 0.0.0.0:8879</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --repo-path</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /local-repo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>Helm Repo可以是任何一个可以下载文件的http服务器</li>
<li>分布式块存储系统<strong>Minio</strong>也可以作为helm Repo服务器, 甚至Github Pages也可以</li>
<li><strong>查看添加更新删除helm Repo</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> local-repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> //myrepo:8879</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> update</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> remove</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> local-repo</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> update</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stable</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>打包发布Chart</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># --save标识是否存在本地Repo, 这条命令会打包出来一个tgz文件</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> package</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --save=false</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># index命令更新index.yaml, 然后把相应的文件放到http服务器上即可</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --url</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> //myrepo:8879</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>彻底删除一个Release</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> del</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --purge</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> websrv-grafana</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>更新Release</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> upgrade</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> values.yaml</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./redis/</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li><strong>查看Release历史并且回滚Release</strong></li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># history子命令能够列出所有的历史版本</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> history</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如需回退特定版本, rollback第二个参数填写该版本即可</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rollback</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> redis-dev</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[DevOps平台Rancher试用笔记]]></title>
            <link>https://code2life.top/blog/0031-rancher-trial</link>
            <guid>https://code2life.top/blog/0031-rancher-trial</guid>
            <pubDate>Tue, 16 Oct 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="devops平台rancher试用笔记" tabindex="-1">DevOps平台Rancher试用笔记 <a class="header-anchor" href="#devops平台rancher试用笔记" aria-label="Permalink to &quot;DevOps平台Rancher试用笔记&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>2020年更新，Rancher版本迭代比较快，删改了部分内容，移步我的知乎专栏文章：<a href="https://zhuanlan.zhihu.com/p/184654439" target="_blank" rel="noreferrer">https://zhuanlan.zhihu.com/p/184654439</a></p>
</blockquote>
<h2 id="rancher的背景和定位" tabindex="-1">Rancher的背景和定位 <a class="header-anchor" href="#rancher的背景和定位" aria-label="Permalink to &quot;Rancher的背景和定位&quot;">&ZeroWidthSpace;</a></h2>
<p>Kubernetes从Borg的原型发展到如今成为&quot;云原生操作系统&quot;这么一个重要的开源项目, 不仅是本身对计算资源抽象带来的分布式计算的变革, 也离不开周边繁荣的生态系统, Rancher就是其中之一. Kubenertes直接解决了集群的容器编排, 统一了资源管理接口, 但没有提供整个运维链的闭环解决方案. Rancher的目的就是提供一整套大而全的解决方案. 不仅囊括的<strong>集群资源的管理</strong>, <strong>还集成日志/监控/预警, 甚至2.x版本加入的持续交付能力</strong>.</p>
<p>这一套解决方案并不需要对K8S本身有非常深刻的了解, <strong>极大的降低了学习成本</strong>. 另外Rancher公司一边提供开源的软件, 一边提供付费的服务, 如果对Kubernetes本身理解到位的话, 出现问题是不需要付费服务就可以解决的.</p>
<h2 id="rancher有哪些好处" tabindex="-1">Rancher有哪些好处 <a class="header-anchor" href="#rancher有哪些好处" aria-label="Permalink to &quot;Rancher有哪些好处&quot;">&ZeroWidthSpace;</a></h2>
<p>笔者一开始了解Kubernetes时, 看了一堆概念和一堆组件, 但还没有把集群搭出来, 因为Kubernetes这套子组件太多了, 手动部署的成本很高. 因此出现了很多自动化部署Kubernetes集群的软件, 比如这些:</p>
<ul>
<li><a href="https://github.com/kubernetes/minikube" target="_blank" rel="noreferrer">minukube</a> 单机玩玩, 不能用于生产环境</li>
<li><a href="https://github.com/kubernetes/kubeadm" target="_blank" rel="noreferrer">kubeadm</a> 据说比较复杂, 相对比较黑盒</li>
<li><a href="https://github.com/gjmzj/kubeasz" target="_blank" rel="noreferrer">kubeasz</a> 完全ansible-playbook部署, 完全白盒, 不用去墙外下载任何东西, 非常适合国情</li>
<li><a href="https://github.com/apprenda/kismatic" target="_blank" rel="noreferrer">kismatic</a> Go封装了ansible以及ssh的细节, 相对白盒, 个性化配置和版本跟进有些不足</li>
<li><a href="https://github.com/rancher" target="_blank" rel="noreferrer">rancher/rke</a> 开箱即用, 兼容云服务商以及独立维护的机器, 比较灵活.</li>
<li>AKS / EKS / GKE 云服务商直接提供高可用的Control Plane (api-server + controller-manager + scheduler + etcd + HA ...)</li>
</ul>
<p>能用在生成环境的大概是后面的4种, <strong>kubeasz和kismatic</strong>适合更专业的有ansible背景的运维人员, <strong>Rancher和EKS等</strong>方案适合小白以及对ansible和kubernetes不够了解的开发和运维人员. 笔者抱着试一试的态度用了两天的Rancher, 发现它的优势不仅是能方便的创建一个集群, 更在于它<strong>降低了开发测试运维人员的&quot;心智成本&quot;来管理集群</strong>. 对比几种管理集群的方案, rancher有天然的优势.</p>
<ul>
<li><strong>kubectl + helm 命令行</strong>, 万金油, 灵活高效, 缺点是有一定学习成本且不够直观, 适合专业的运维人员</li>
<li><strong>直接调用 kubernetes API</strong>, 可用于二次开发, 学习开发成本很高</li>
<li><strong>kubernetes-dashboard</strong>, 仅支持cluster资源对象管理, 不够全面</li>
<li><strong>rancher</strong> 多集群用户/资源管理-服务编排-监控预警-持续交付平台</li>
</ul>
<p>它的功能特性优点确实如官网的宣传一样, 名副其实了. 这是几条体验比较深的:</p>
<ul>
<li>Install and manage Kubernetes clusters on any infrastructure.</li>
<li>Provision and manage GKE, AKS, and EKS clusters</li>
<li>Centralized Security Policy Management</li>
<li>Monitoring, capacity management and alerts</li>
<li>Complete UI for Managing Workloads</li>
<li>User Projects Spanning Multiple Namespaces</li>
<li>Integrated CI/CD Pipelines</li>
</ul>
<h2 id="rancher平台试用经历" tabindex="-1">Rancher平台试用经历 <a class="header-anchor" href="#rancher平台试用经历" aria-label="Permalink to &quot;Rancher平台试用经历&quot;">&ZeroWidthSpace;</a></h2>
<p>首先一行命令把Rancher跑起来</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sudo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --restart=unless-stopped</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 80:80</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 443:443</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> rancher/rancher</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>然后创建一个集群, 我这本机VM中运行, 所以选择了Custom. 输入完集群的基础配置之后, Rancher会提供另一条docker命令, 拿到节点上运行, 初始化过程是这样的:</p>
<ul>
<li>每个执行的节点上, 创建一个叫<strong>rancher-agent</strong>的<strong>特权容器</strong>,</li>
<li>这个容器会继续创建rke-tools的容器, 然后用它来<strong>初始化容器的宿主机各个组件</strong>, 比如kubelet, kube-proxy, api-server等等</li>
<li>在rancher上会同步显示每个步骤的信息, 整个过程无需干预, 网络不是太差的话, 坐等10分钟集群就出来了.</li>
</ul>
<p>Rancher接管集群的方式非常多样, 下图的<strong>上半部分</strong>是用于<strong>整合导入现有的Kubernetes集群</strong>, <strong>下半部分</strong>是用于<strong>接管集群的初始化工作, 直接部署出来一套集群</strong>, 这样出来的Control Plane组件都是基于<strong>hypekube完全运行在docker中的</strong>(RancherOS也是这个思路, 整个操作系统全部容器化了)</p>
<p><img src="//filecdn.code2life.top/rancher-create-cluster.jpg" alt="create-cluster"></p>
<p>平台从上而下分为多个层级, 每个层级的管理的粒度从粗到细, 一目了然</p>
<ul>
<li><strong>Global</strong> 级别下管理各个集群整体配置, 全局设置, <strong>用户和角色</strong>等</li>
<li><strong>Cluster</strong> 级别下管理节点及<strong>节点调度</strong>, 项目, 命名空间, 用户及权限, <strong>存储</strong>, 单<strong>集群级别的监控预警/通知</strong>, kubectl接口等</li>
<li><strong>Project</strong> 级别主要功能是管理<strong>单个项目下各个命名空间</strong>的Kubernetes资源对象和项目成员, 包括运维管理<strong>最核心的功能</strong></li>
</ul>
<p>这是一个Global级别管理Role的例子<br>
<img src="//filecdn.code2life.top/rancher-global-level-manage.jpg" alt=""></p>
<p>这是<strong>Cluster级别管理</strong>的概况
<img src="//filecdn.code2life.top/rancher-overall-cluster.jpg" alt=""></p>
<p>这是<strong>Cluster级别管理预警</strong>的例子, 预警条件和各种类型通知已经非常灵活了, 另外<strong>Cluster级别统一管理PV/StorageClass, 符合运维的场景</strong>
<img src="//filecdn.code2life.top/rancher-cluster-level-manage.jpg" alt=""></p>
<p>这是Project级别的工作负载情况, 有很多方便用户的细节设计. 对于每种Workload有方便的功能入口, 比如redeploy, rollback, scale, Endpoint链接, yaml编辑查看, shell到容器内部执行命令等.
<strong>每个workload, 也就是真正会产生Pod的资源</strong>, 展开都会有圆点和方块图标, 每个<strong>圆点</strong>代表一个<strong>container容器</strong>, Pod只有一个container的不会显示方块, 此时一个圆点也就是一个Pod, 对于有多个容器的Pod, 比如Init container或带有sidecar的, 会显示方块, 每个<strong>方块</strong>是一个<strong>Pod</strong>, <strong>hover上去有便捷的入口</strong>
<img src="//filecdn.code2life.top/rancher-workloads.jpg" alt=""></p>
<p>Project级别新建一个Deploy, 相当于deployment的语法糖, 直接表单配置很直观, 不懂Kubenetes也可以玩. 通过表单很方便的设置<strong>探针, 资源限制, Node Selector等</strong>, 而无需了解Kubernetes的语法结构 (感觉这是rancher的套路, 把功能做的很方便, 用户形成依赖之后不会搞原生kubernetes了, 然后出问题得找他们付费支持)
<img src="//filecdn.code2life.top/k8s-deployment.jpg" alt="dep"></p>
<p><strong>Pipeline功能</strong>, 以及其他Resource管理入口, 话不多说上图
<img src="//filecdn.code2life.top/rancher-pipeline.jpg" alt=""></p>
<p><strong>查看Pod日志, 以及完整的Yaml</strong>, 比Dashboard多了一下细节功能, 比如导出
<img src="//filecdn.code2life.top/rancher-viewlogs.jpg" alt="">
<img src="//filecdn.code2life.top/rancher-k8s-yaml.jpg" alt=""></p>
<p>除了截图的这些功能, 还有一些重要的功能比如:</p>
<ul>
<li>右上角用户可以管理<strong>kubectl登陆</strong>的Key和token</li>
<li>Rancher自己整了一个Project的概念, <strong>每个Project再分namespace</strong>, Member及其权限控制更符合真实场景</li>
<li>Project级别下, 集成了运维的核心概念, 归纳一下,
<ul>
<li><strong>Workloads和Catalog App</strong>管理的是动态的资源, <strong>真实的容器和服务</strong></li>
<li><strong>Resource</strong>下管理的主要是<strong>相对变化较少的部署资源, 相对静态</strong>, 比如ConfigMap/Secret, 此外还有<strong>Pod/Depoloyment级别的预警配置</strong>, 以及日志集成等, 资源管理还有<strong>证书的管理</strong>, 能很方便的做SSL Ingress, SSL offloading等</li>
</ul>
</li>
<li><strong>LoadBalancing</strong> 直观的展示的当前的Ingress和反向代理配置, 是<strong>对外可见的服务</strong>,</li>
<li><strong>Service Discovery</strong> 是内部的服务汇总, 包括集群内部分配的DNS以及固定的ClusterIP等, 是<strong>对内可见的服务</strong></li>
</ul>
<h2 id="rancher对helm的支持" tabindex="-1">Rancher对Helm的支持 <a class="header-anchor" href="#rancher对helm的支持" aria-label="Permalink to &quot;Rancher对Helm的支持&quot;">&ZeroWidthSpace;</a></h2>
<p><a href="https://docs.helm.sh" target="_blank" rel="noreferrer">Helm</a> 如今以及成为kubernetes集群必不可少的一个组件了, 包括一个客户端cli程序和一个服务端(Tiller镜像的Deployment), 每个应用声明为一个Chart, 通过动态模板隔离了<strong>配置</strong>和<strong>编排架构</strong>, 并且可以统一管理发布以及<strong>版本控制</strong>, 减少了大量的运维成本.</p>
<p>Rancher平台<strong>Catalog App</strong>的功能就是对Helm的集成, Global级别下点击Catalogs打开<strong>Helm Stable</strong>仓库可以获取到更多的Helm Chart, Helm Incubator不要开, 质量明显不如Helm Stable, 也可以添加自定义的Helm仓库. 然后, 在项目级别的Catalog App里, 可以Launch很多第三方Chart, 分分钟部署好集群基础设施.
<img src="//filecdn.code2life.top/catalog-app.jpg" alt=""></p>
<p>虽然Catalog App方便透顶, 但笔者以为重要的基础设施还是自行维护, helm install去创建比较好, 或者用第三方云服务商的现成的服务, 因为<strong>基础组件的稳定性和运维要求更高, 要么就吃透, 要么就不要自己维护</strong>, 用Rancher Launch一个哨兵模式Redis还好, 但是Launch一个EFK, 没有一定背景知识不一定可以完全理解, 出了问题就一脸懵逼了.</p>
<p>在RKE创建的Kubernetes(hypekube)集群下, 自己玩helm和别的一样,</p>
<ul>
<li>客户端~/.kube/config 设置好context &amp; user &amp; cluster 与 kubectl 共享配置访问集群
下</li>
<li>在集群上先创建tiller作为serviceaccount, 用于管理helm相关的应用</li>
<li>下载helm客户端, init一下就完事了</li>
<li><a href="https://docs.helm.sh/using_helm/#using-ssl-between-helm-and-tiller" target="_blank" rel="noreferrer">TLS双向认证的helm</a>参考文档配置</li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kube-system</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> serviceaccount</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clusterrolebinding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --clusterrole</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  cluster-admin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  --serviceaccount=kube-system:tiller</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">helm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --service-account</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tiller</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>试用过程中也发现了Rancher的一些<strong>局限性</strong>, 而且因为它以及Kubernetes不是很熟悉, 遇到了一些坑, 解决的办法以及心得在这里分享一下</p>
<ul>
<li>Rancher本身比较耗资源, 运行时占用&gt;1G memory. 最好单独部署, 或运行容器时限制cpu和内存, <strong>防止影响宿主机</strong></li>
<li>如果用Rancher创建集群, Ingress Controller默认会帮我们创建好, 但Ingress Controller初始配置不一定是想要的(比如默认80端口且创建好后无法修改), 建议yaml自行维护Ingress Controller, 具体的Ingress配置可以在Rancher的UI中配, 很方便</li>
<li>Workload里面StatefulSet的<strong>Scale按钮扩容没有提示</strong>, 一按就会修改StatefulSet开始调度, 慎点, StatefulSet一般都是像Redis, MySQL这种应用, 的<strong>权限控制</strong>应该要更严一点</li>
<li>PV的信息最好加上Node Affinity, PVC加上Label Selector等, 防止PVC申请到错误的PV.</li>
<li>Rancher使用目前没遇到大的问题, 小问题比如Catalog App状态更新的时候有延迟等</li>
<li>需要防止Pod调度导致其他Pod<strong>饿死</strong>的情况, 比如像ElasticSearch启动时非常占资源, 节点调度和资源限制策略需要注意, 比如用<strong>LimitRange</strong>控制单个资源, <strong>ResourceQuota</strong>控制整个namespace</li>
<li>集群调试方面, 如果Workload无法正常运行或不停的CrashBackoff, 有一些常见的调试方法如下:
<ul>
<li>Kubernetes本身是否有报错, Rancher平台或Dashboard可以<strong>直接看到</strong></li>
<li>Container的log是否有error, 通过<strong>栈轨迹</strong>分析错误原因</li>
<li>PVC绑定的PV, 对应的Path/Disk是否有<strong>读写权限</strong>等等</li>
<li>大部分常见问题能够在Rancher平台上直接定位, 但不排除比较难搞的问题还是需要<strong>kubectl v+日志级别</strong> 调试, <strong>kubectl describe</strong>查看资源状态</li>
<li>终极办法, 到节点上直接运行docker或其他运维命令, 这种比较底层的定位问题的方法能够直接看到进程和文件, 避免了&quot;应用不知道被集群调度到哪去了&quot;的不安全感~</li>
</ul>
</li>
</ul>
<p>最后来个用Rancher部署的Kubernetes集群基础组件全家福吧<br>
<img src="//filecdn.code2life.top/rancher-catalogs.jpg" alt=""><br>
<img src="//filecdn.code2life.top/rancher-dashboard.jpg" alt=""><br>
<img src="//filecdn.code2life.top/rancher-grafana.jpg" alt=""><br>
<img src="//filecdn.code2life.top/rancher-consul.jpg" alt=""><br>
<img src="//filecdn.code2life.top/rancher-minio.jpg" alt=""><br>
<img src="//filecdn.code2life.top/rancher-EFK.jpg" alt=""></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Golang学习笔记] 代码片段记录-入门级开发]]></title>
            <link>https://code2life.top/blog/0028-go-snippets-1</link>
            <guid>https://code2life.top/blog/0028-go-snippets-1</guid>
            <pubDate>Wed, 01 Aug 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="golang学习笔记-代码片段记录-入门级开发" tabindex="-1">[Golang学习笔记] 代码片段记录-入门级开发 <a class="header-anchor" href="#golang学习笔记-代码片段记录-入门级开发" aria-label="Permalink to &quot;[Golang学习笔记] 代码片段记录-入门级开发&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>最近迷上了Go语言, 看完《The Go Programming Language》后收益颇丰。作为一个<strong>极其懒惰</strong>的程序猿, 我比较认同Golang提倡的&quot;Less is more&quot;的哲学, 而且Golang确实是一门非常有意思的语言, 集众家之长而又标新立异. 本系列记录一下在学习实践Go语言过程中使用到的一些代码片段，边学边记， <strong>不仅限于标准库, 还包括真正开发过程中常用的各类组件的用法</strong>。</p>
<h4 id="go-语言特性" tabindex="-1">Go 语言特性 <a class="header-anchor" href="#go-语言特性" aria-label="Permalink to &quot;Go 语言特性&quot;">&ZeroWidthSpace;</a></h4>
<p>作为一个更熟悉其他编程语言的猿, 初入Golang发现有很多跟我之前用的一些主流编程语言差异很大的特性, 在开始编写Golang程序之前, 首先需要<strong>铭记</strong>这些特性:</p>
<ul>
<li>没有继承</li>
<li>没有重载</li>
<li>没有泛型</li>
<li>没有枚举</li>
<li>没有class / private / protected / public / static</li>
<li>没有getter / setter</li>
<li>没有函数参数默认值</li>
<li>没有三元判断操作符</li>
<li>没有lambda表达式</li>
<li>没有try / catch / finally</li>
<li>没有线程</li>
<li>没有while循环</li>
<li>不存在引用传递, 传指针也是复制指针值</li>
<li>switch / case 不需要写break</li>
<li>if / for 不需要加括号</li>
</ul>
<p>另外, <strong>牢记</strong>这些Go中常用关键字, 内置函数, 类型等</p>
<ul>
<li>make() / len() / cap() / append() / copy() delete() / close()</li>
<li>const / iota / rune / interface / struct / map</li>
<li>range / select / chan / close / go</li>
<li>defer / panic</li>
</ul>
<p>注意：关于值传递和引用传递，有一个非常简单的解释：<strong>能否有两个变量指向同一个内存地址</strong>，比如在C++是存在引用传递的，而Java是不存在引用传递的的。</p>
<p><strong>Golang同样不存在引用传递</strong>。比如下面两种形式的值传递，第二个func传递的值是对象的地址，两种方式都可以用点号访问，地址传过去也不需要C++的 &quot;-&gt;&quot; 符号访问成员变量和方法。星号(指针)和取址操作符（&amp;）与C/C++类似。</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Example</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  property </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Param</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">example </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Example</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) passCopyOfCaller (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">passCopyOfParam</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 这里修改调用对象和参数调用结束后是没有效果的</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">example </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Example</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) passAddressCaller (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">passAddressOfParam</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 这里可以直接修改调用对象，以及参数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><h4 id="参考链接" tabindex="-1">参考链接 <a class="header-anchor" href="#参考链接" aria-label="Permalink to &quot;参考链接&quot;">&ZeroWidthSpace;</a></h4>
<p>此处顺便记录一下学习Go语言过程中比较有用的一些资源链接, 包括部分代码片段的参考链接</p>
<ul>
<li><a href="https://books.studygolang.com/The-Golang-Standard-Library-by-Example/" target="_blank" rel="noreferrer">《Go语言标准库》</a></li>
<li><a href="https://github.com/topics/go?o=desc&amp;s=stars" target="_blank" rel="noreferrer">Github关于Go所有开源项目</a></li>
<li><a href="https://studygolang.com/articles/10523" target="_blank" rel="noreferrer">Go依赖包管理工具对比</a></li>
<li><a href="https://go-search.org/" target="_blank" rel="noreferrer">Go开源项目搜索Go-search</a></li>
<li><a href="//localhost:8000" target="_blank" rel="noreferrer">开启本地文档服务器: godoc -http :8000</a></li>
</ul>
<h2 id="概述" tabindex="-1">概述 <a class="header-anchor" href="#概述" aria-label="Permalink to &quot;概述&quot;">&ZeroWidthSpace;</a></h2>
<p>本篇要记录一些入门级的基本操作和Golang中常用代码逻辑写法, 如数据结构定义, 对象操作, 日期时间, 枚举, 异常处理, 类型转换等等. 以及一些未归类的杂项, 比如Slice的用法, Unsafe指针操作, 反射等等。</p>
<h2 id="日常编程实用片段" tabindex="-1">日常编程实用片段 <a class="header-anchor" href="#日常编程实用片段" aria-label="Permalink to &quot;日常编程实用片段&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="错误处理" tabindex="-1">错误处理 <a class="header-anchor" href="#错误处理" aria-label="Permalink to &quot;错误处理&quot;">&ZeroWidthSpace;</a></h4>
<p>先从Golang代码中出现频率最高的语句开始 😃</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h4 id="枚举定义" tabindex="-1">枚举定义 <a class="header-anchor" href="#枚举定义" aria-label="Permalink to &quot;枚举定义&quot;">&ZeroWidthSpace;</a></h4>
<p>Golang没有枚举，一般使用常量定义&quot;枚举&quot;</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// StateEnum : define a enum type alias</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> StateEnum</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> uint</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// use iota to auto increase,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// use bit operation to make flag options</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  A</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> StateEnum</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> iota</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  B</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  C</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 一般情况下从iota + 1开始即可, 防止默认0值带来的问题</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  X</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> iota</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">iota</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // 0, 1</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  J</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">K</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                  // 1, 2</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  M</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">N</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">20</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">         // 10,20 (iota also +1)</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  R</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> iota</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x3C;&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      // 3 &#x3C;&#x3C; 4 = 3 * 2^4 = 48</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  S</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                     // 4 * 2^4 = 64</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  P</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                     // 5 * 2^4 = 80</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  state </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> StateEnum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(state </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> A)                      </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, A, B, C)           </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1 10 100</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%03b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, A</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">B, B</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">C, A</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">B</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">C) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//011 110 111</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(X, Y, M, N, R, S, P)             </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0 1 10 20 48 64 80</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div><h4 id="日期时间" tabindex="-1">日期时间 <a class="header-anchor" href="#日期时间" aria-label="Permalink to &quot;日期时间&quot;">&ZeroWidthSpace;</a></h4>
<p>标准库time包的使用，包括定时器，日期时间以及时区的转换处理</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 获取当前时间</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  t </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"current time: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, t)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 生成一个不会停止的定时器, chan Time类型</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 如需手动停止, 使用time.NewTicker()函数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  tick </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Tick</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Second)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Sleep 1s</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Second)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  go</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> range</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tick {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tick"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Millisecond)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Format 与 Parse</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Go 中以固定参考时间作为时间格式化和转换的layout, 非常奇怪的设定</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 固定的参考时间是: 2006年1月2日下午3点4分5秒(时区Mountain Standard Time)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // https://stackoverflow.com/questions/20234104mmss-format</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  layout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "2006-01-02 15:04:05"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, t.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2006-01-02 15:04:05PM MST"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 注意时区的差别, 尽量使用ParseInLocation</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  t2, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(layout, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2018-01-01 20:05:30"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  t3, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ParseInLocation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(layout, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2018-01-01 20:05:30"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, time.Local)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pacific, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LoadLocation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"America/Los_Angeles"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  t4, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ParseInLocation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(layout, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2018-01-01 20:05:30"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, pacific)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // output:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 2018-01-01T20:05:30Z </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 2018-01-01T20:05:30+08:00 </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 2018-01-01T20:05:30-08:00</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, t2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time.RFC3339),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    t3.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time.RFC3339),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    t4.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time.RFC3339))</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 时间间隔计算</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Duration 是一个int64的别名, 单位是纳秒</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> span </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Duration</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  span, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ParseDuration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"+10ms"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  tn </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> t2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(span)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  span2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tn.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sub</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(t2)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  span2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Millisecond</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // output: 10ms 20ms</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, span, span2)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 下面的Since函数相当于 time.Now().Sub(t2)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Since</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(t2)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 另外, 诸如 Truncate() 之类可能常用的函数自行查阅godoc</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // https://godoc.org/time#Duration.Truncate</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // wait 10s and then exit</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  &#x3C;-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewTimer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Second).C</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br></div></div><p>常用的开源库之一，定时任务<a href="https://github.com/robfig/cron" target="_blank" rel="noreferrer">Cron Job</a></p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">github.com/robfig/cron</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyScheduleJob</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">MyScheduleJob</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"calc next"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.Second)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">MyScheduleJob</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"run job"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  tz, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LoadLocation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Asia/Chongqing"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // cron.New()亦可, 但是用默认的Local时区</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> cron.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewWithLocation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tz)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // cron 表达式语法: 秒 分 时 天 月 周几</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // https://blog.csdn.net/weixin_40426638/article/details/78959972</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Field name   | Mandatory? | Allowed values  | Allowed special characters</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Seconds      | Yes        | 0-59            | * / , -</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Minutes      | Yes        | 0-59            | * / , -</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Hours        | Yes        | 0-23            | * / , -</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Day of month | Yes        | 1-31            | * / , - ?</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Month        | Yes        | 1-12 or JAN-DEC | * / , -</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">AddFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0 30 * * * *"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Every hour on the half hour"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) })</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @yearly (or @annually) = 0 0 0 1 1 *</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @monthly = 0 0 0 1 * *</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @weekly = 0 0 0 * * 0</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @daily (or @midnight) = 0 0 0 * * *</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @hourly = 0 0 * * * *</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">AddFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"@hourly"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Every hour"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) })</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // @every Duration, 相当于time.NewTicker(time.ParseDuration("1h30m"))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">AddFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"@every 1h30m"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Every hour thirty"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">AddFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"@daily"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Every day"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) })</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 获取当前CronTable的详情</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  entries </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Entries</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, entries[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">].Next) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// CST</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Location</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 自定义ScheduleJob</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  j </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyScheduleJob</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  c.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Schedule</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(j, j)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Scanln</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br></div></div><h4 id="集合排序" tabindex="-1">集合排序 <a class="header-anchor" href="#集合排序" aria-label="Permalink to &quot;集合排序&quot;">&ZeroWidthSpace;</a></h4>
<p>标准库sort包排序以及自定义排序规则</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bytes</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">io/ioutil</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sort</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">golang.org/x/text/encoding/simplifiedchinese</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">golang.org/x/text/transform</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">s </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(s) }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">s </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Swap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">j</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { s[i], s[j] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> s[j], s[i] }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">s </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Less</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">j</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  a, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UTF82GBK</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(s[i])</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  b, _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UTF82GBK</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(s[j])</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  bLen </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> idx, chr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> range</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> idx </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> bLen</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> chr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b[idx] {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> chr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b[idx]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//UTF82GBK : transform UTF8 rune/string into GBK byte array</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UTF82GBK</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">src</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) ([]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  GB18030 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> simplifiedchinese.All[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ioutil.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ReadAll</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(transform.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(src)), </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    GB18030.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewEncoder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//GBK2UTF8 : transform  GBK byte array into UTF8 string</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> GBK2UTF8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">src</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  GB18030 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> simplifiedchinese.All[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  bytes, err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ioutil.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ReadAll</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(transform.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(src), </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    GB18030.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewDecoder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()))</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes), err</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">9</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">6</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"哈"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"呼"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"嚯"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ha"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">","</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 基本类型的排序</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Ints</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"After sorted: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, a)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // sort.Slice 自定义排序函数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">j</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a[j]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DESC: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, a)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Strings</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Default: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, b) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// [, ha 呼 哈 嚯]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 自定义排序规则</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"By PinYin: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, b) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// [, ha 哈 呼 嚯]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 倒序排</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sort</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Reverse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ByPinyin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b)))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Reverse: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, b) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// [嚯 呼 哈 ha ,]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 二分查找第一个满足条件的索引</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  idx </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sort.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Search</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b), </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "哈"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(idx)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br></div></div><h4 id="集合的创建和修改" tabindex="-1">集合的创建和修改 <a class="header-anchor" href="#集合的创建和修改" aria-label="Permalink to &quot;集合的创建和修改&quot;">&ZeroWidthSpace;</a></h4>
<p>Golang中大部分场景使用<strong>切片数据类型（Slice）<strong>进行集合操作, 只有</strong>初始化指定了长度的数组才是数组类型</strong><br>
数组或切片都不能越界访问, 否在会报错: index out of range / slice bounds out of range<br>
**内置函数或切片语法（make cap len copy append delete s[m:n] ... ）**能够支持简单的集合操作, 但复杂的操作一般自己封装遍历方法，或借助第三方库实现，比如：</p>
<ul>
<li>Go-Linq，用法与C#的杀手锏Linq非常像，但是Golang没有泛型，所以库提供的函数参数是interface{}, 使用起来需要大量类型断言不如C#优雅，带T的函数如&quot;WhereT&quot;等会损失更多性能，其文档在这里：<a href="https://github.com/ahmetb/go-linq" target="_blank" rel="noreferrer">https://github.com/ahmetb/go-linq</a></li>
<li>Gen自动生成某个具体类型的集合操作的代码是个性能更高的方案（与泛型的原理很像）：<a href="https://github.com/clipperhouse/gen" target="_blank" rel="noreferrer">https://github.com/clipperhouse/gen</a>。</li>
</ul>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 注：相关内置函数的参数和返回值</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(Slice, n)       slice      slice of type T with length n and capacity n</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(Slice, n, m)    slice      slice of type T with length n and capacity m</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(Map)    创建一个没有键值对的空map</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(Map, n) 创建初始大小N的map</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(channel)    创建一个没有缓冲的 channel</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func make(channel, n) 创建一个缓冲N个数据的 channel</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func new() *Type 一般不需要用new函数初始化, new([]string) 亦可初始化slice</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func len(Slice / Array / Map) int 返回map / slice / array的长度</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func cap(Slice) int 返回map或slice的容量</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func append(Slice, Element...) int 向Slice追加值, 不定参数</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func copy(Slice, Slice)  第二个Slice的值复制到第一个Slice, 但以第一个Slice的长度为准</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func delete(Map[KeyType]ValueType, *KeyType) 删除字典元素</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// func close(channel) 关闭channel, 关闭后 "&#x3C;-" 操作符第二个返回值 isClosed 为true</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> length: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  capacity: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, arr, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr), </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //output: 0x0, len: 0 cap: 0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> length: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  capacity: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, arr, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr), </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //output: 0x-xxx len: 100, cap: 200</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 切片尾部追加元素append, 如果capacity不够, 在一定范围内, 自动扩大一倍</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 内置函数对Slice的操作是没有副作用的，返回的新的Slice</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr, fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sprintf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"element-1"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 利用append不定参，以及类似Python语法的切片类型切片操作可以实现相对复杂的操作</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  index </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 50</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 使用append删除中间第50个元素</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr[:index], arr[index</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //使用append在某处插入一个元素</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  rear </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}, arr[index</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr[:index], </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(arr[:index], rear</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Copy有点C++的影子</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"hello world"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  copy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(str, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"haha "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(str)) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// haha  world</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br></div></div><h4 id="结构体-对象的复制" tabindex="-1">结构体/对象的复制 <a class="header-anchor" href="#结构体-对象的复制" aria-label="Permalink to &quot;结构体/对象的复制&quot;">&ZeroWidthSpace;</a></h4>
<p>对象的浅拷贝(Shadow Copy)</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PlainObj</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  sub </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SubObj</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubObj</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  substr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  src </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PlainObj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"str"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SubObj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"substr"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  target </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PlainObj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //shadow copy</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">target </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">src</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  target.sub.substr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "newstr"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(src)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(target)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //output: &#x26;{str {substr}}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //output: &#x26;{str {newstr}} </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><p>对象的深拷贝</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 通过gob序列化反序列化实现简单的深拷贝</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> deepCopy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">dst</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">src</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> buf </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Buffer</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> gob.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewEncoder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">buf).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Encode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(src); err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> err</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> gob.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewDecoder</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewBuffer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(buf.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Decode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(dst)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h4 id="类型转换及类型断言" tabindex="-1">类型转换及类型断言 <a class="header-anchor" href="#类型转换及类型断言" aria-label="Permalink to &quot;类型转换及类型断言&quot;">&ZeroWidthSpace;</a></h4>
<p>在字符串的转换中，rune类型是Golang比较有特色的地方，字符串强转成[]byte就是<strong>字节</strong>数组，UTF-8中文字符大部分会变成3个Byte；而强转成[]rune类型则是<strong>字符</strong>数组，每个值表示一个Unicode，而UTF-8是最常见的实现方式，因此rune数组输出出来<strong>等于Unicode码点</strong>，而真实存储的是变长的UTF8表示的byte数组，如下所示</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "字符串 string"</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//类型强制转换</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sliceByte </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">byte</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(str)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sliceRune </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">rune</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(str)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sliceByte)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 底层字节数组 output: [229 173 151 231 172 166 228 184 178 32 115 116 114 105 110 103]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sliceRune[:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">])</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// output: 23383, 表示"字"的Unicode码点（U+5B57 / &#x26;#23383;），16进制为：[E5 AD 97] = [229 173 151]</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 码点和进制转换工具：https://unicode-table.com/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sliceRune[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Rune代表一个字符，output: 串 string</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//相同底层结构的基本类型或结构体直接可以互转</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> num1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int32</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int64</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int64</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(num1)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">arg </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //switch方式的类型断言</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  switch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arg.(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  case</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ref str: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %T</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, arg, arg)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  case</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ref int: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %T</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, arg, arg)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"unknown type: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%T</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, arg)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //if方式的类型断言, 最好判断ok返回值增强鲁棒性</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> str, ok </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arg.(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); ok {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, str)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> num, ok </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arg.(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); ok {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, num)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"any value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//传入参数的隐式转换为interface{}类型</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br></div></div><h4 id="panic的处理和恢复" tabindex="-1">panic的处理和恢复 <a class="header-anchor" href="#panic的处理和恢复" aria-label="Permalink to &quot;panic的处理和恢复&quot;">&ZeroWidthSpace;</a></h4>
<p>一般的异常可以返回值判断error，但fatal error，一般用panic + recover的方式处理，defer来确保所有退出条件都会执行，defer其实是在return之后执行的，比较违反常识，细节参考: <a href="https://blog.csdn.net/qq_22063697/article/details/74892728" target="_blank" rel="noreferrer">https://blog.csdn.net/qq_22063697/article/details/74892728</a></p>
<p><strong>注：因为Runtime遇到defer的函数是压栈的，所以return之后当前栈弹出，再依次后进先出执行defer的函数列表，这就解释了为什么是在return之后执行，与很多语言的try-catch-finally在同一个调用栈是不一样的</strong></p>
<p>Golang错误和异常处理的规范和原则: <a href="https://www.jianshu.com/p/f30da01eea97" target="_blank" rel="noreferrer">https://www.jianshu.com/p/f30da01eea97</a></p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 注意： defer 直接跟recover()是不行的</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">defer</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //recover 能够捕获到当前goroutine产生的所有panic异常</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> recover</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); err </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Panic recover! p: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, err, debug.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Stack</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Using "log.Fatalf" is better (will call os.Exit(1))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}()</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h2 id="杂项" tabindex="-1">杂项 <a class="header-anchor" href="#杂项" aria-label="Permalink to &quot;杂项&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="指针操作" tabindex="-1">指针操作 <a class="header-anchor" href="#指针操作" aria-label="Permalink to &quot;指针操作&quot;">&ZeroWidthSpace;</a></h4>
<p>不安全的指针操作与直接运算 (非常危险, 尽量不要用)</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PlainObj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{str: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// unsafe.Pointer 任意类型指针, 可转换任意类型但不能直接进行指针运算</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 强转之后的指针操作有风险, 很容易像C/C++一样飞掉</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PlainObj2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)(unsafe.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Pointer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj1))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// uintptr 任意指针并且可以直接参与指针运算</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> uintptr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(unsafe.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Pointer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj1))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 此时ptr指向4个byte后的地址, 可能是struct的第二个属性</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> unsafe.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sizeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int32</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h4 id="interface与反射操作" tabindex="-1">interface与反射操作 <a class="header-anchor" href="#interface与反射操作" aria-label="Permalink to &quot;interface与反射操作&quot;">&ZeroWidthSpace;</a></h4>
<p>因为Golang是Duck Type多态，比Java/C#这种通过层层接口继承的方式要灵活很多。<strong>多态是OOP中常用的概念，即同一个函数/方法，在不同对象时表现出来不同的行为</strong>，Golang有两种方式实现：</p>
<ul>
<li>第一种方式：定义一个interface，不同的对象只要有这个interface定义的方法就可以调用，这种多态与Java/C#这种传统OOP语言思路完全不一样</li>
<li>第二种方式：直接传递interface{}参数（类似C#的dynamic类型），通过类型断言，反射来处理。这可能带来interface{} 满天飞，到处是类型转换和类型断言的问题，所以能用第一种方式做就用第一种方式，<strong>不要滥用interface{}</strong></li>
</ul>
<p>第一种方式的例子，来自 <a href="https://gobyexample.com/interfaces" target="_blank" rel="noreferrer">https://gobyexample.com/interfaces</a></p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">math</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> geometry</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    area</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">float64</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> rect</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    width, height </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">float64</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> circle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    radius </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">float64</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">r </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">rect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">area</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">float64</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.width </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.height</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">c </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">circle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">area</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">float64</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> math.Pi </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c.radius </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c.radius</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// measure 函数的参数是一个interface</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 实现了这个interface的struct都能作为入参</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> measure</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">g</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> geometry</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(g.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">area</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    r </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> rect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{width: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, height: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    c </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> circle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{radius: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    measure</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(r)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    measure</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(c)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br></div></div><p>第二种方式，func measure(g interface{}) interface{}的类型断言[ x.(Type) ]在前几节有示例了，
这里再尝试一下标准库reflect包的使用</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reflect</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Ref</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Expose </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Ref</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">DoSth</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"reflect !"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  refMap </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  refMap[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"a"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Ref</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  refMap[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Ref</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //delete(m, "b")</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1. Finish init map </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> %v\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(refMap), refMap)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // reflect 包提供的Type和Value类型是反射的关键</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  typeOfA </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> reflect.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">TypeOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(refMap[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"a"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">])</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  valueOfB </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> reflect.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ValueOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(refMap[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">])</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">2. Print Name() Kine() String()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"refMap['a'] type is: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, typeOfA, typeOfA.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"refMap['b'] kind is: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, reflect.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">TypeOf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(refMap[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Kind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"refMap['b'] value: "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, valueOfB)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //1. Finish init map 2 map[a:{1} b:0xc042068088]</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //refMap['a'] type is:  main.Ref Ref</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //refMap['b'] kind is:  ptr</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //refMap['b'] value:  &#x3C;*main.Ref Value></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">3. MethodByName() and Call()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  method </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">MethodByName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DoSth"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  method.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([]</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reflect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //reflect !</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //只有struct可以调用NumField</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">4. NumField() and Field() / FieldByName()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> typeOfA.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NumField</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    field </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> typeOfA.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Field</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //field.Tag .PkgPath .Index .Offset etc.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"field '</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">' type is :</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, field.Name, field.Type)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //field 'Expose' type is :int</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Interface() 可以转回interface类型</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 指针类型的reflect.ValueOf()之后, 必须调用Elem()才能拿到指针对应的值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">5. NumField() and Field() for struct pointer"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Elem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NumField</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Elem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Field</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    value.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SetInt</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">33</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"canset: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, value.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CanSet</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(), value.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //canset: true value: &#x3C;int Value></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">6. Invoke Function by Call()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NumMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    method </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> valueOfB.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Method</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //could use reflect.ValueOf(1) to add parameters</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    method.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([]</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reflect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //reflect !</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br></div></div><h4 id="标准库container包的数据结构" tabindex="-1">标准库container包的数据结构 <a class="header-anchor" href="#标准库container包的数据结构" aria-label="Permalink to &quot;标准库container包的数据结构&quot;">&ZeroWidthSpace;</a></h4>
<p>标准库container包的有一些常用的数据结构的实现比如链表, 环形队列, 堆等，示例来自Golang自带的Godoc</p>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">container/list</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">container/ring</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 创建并初始化双向链表</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  l </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> list.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  e4 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PushBack</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  e1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PushFront</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  e3 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">InsertBefore</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, e4)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">InsertAfter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, e1)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 遍历链表, 有点像C艹</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> e </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Front</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); e </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; e </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> e.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(e.Value)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 移动或删除链表元素</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 还有其他各种操作比如:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // MoveBefore MoveToFront MoveToBack</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">MoveAfter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(e3, e1)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Remove</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(l.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Back</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 环形链表, 数据结构与双向链表有相似之处</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 除了算法课上的经典约瑟夫环问题, 实际应用暂时还不知道哪里能用到</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // https://blog.csdn.net/u010781856/article/details/46611029</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  r </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ring.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 初始化环形链表的内容</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    r.Value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    r </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 正数或负数, 移动链表的当前指针</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  r </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Move</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // NOTE: 我阅读Golang源码发现list调用Len()复杂度为O(1)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 但ring调用Len()时间复杂度为O(N), 这是因为ring的指针本身就是一个Ring,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 而list的数据结构是一个保护Element root以及一个int类型的length组成</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 所以ring的Move()实现的是直接for循环传入的n而不是循环n % Len()个值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">val</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v, ok </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> val.(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); ok {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      // 1 2 3 4 0</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, v)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 没完全搞明白, 大概就是去删掉或添加元素</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  r2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Unlink</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// r2: 2 3  r: 1 4 0</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  r </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Link</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(r2) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// r: 4 0 1 2 3</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br></div></div><p>container包的heap是一个抽象的堆,可以实现部分接口创建一个自定义的堆结构, 对于最大/最小堆有一些知识点顺便记录一下</p>
<ul>
<li>二叉堆可以实现优先级队列, 堆结构是一种完全二叉树(N-1层全满, N层数据全在左侧)</li>
<li>堆的主要操作有上浮和下沉, 初始化堆即从第一个非叶子节点(n/2 - 1)开始下沉, 直至根节点</li>
<li>插入堆只需插入最后一个位置然后上浮即可</li>
<li>堆取顶或Pop操作, 只需调换首尾, 取尾并使根节点下沉</li>
<li>堆排序算法的思想就是对一个最大/最小堆不断取顶得出排序后的序列</li>
</ul>
<p>也顺便复习一下几种树相关的概念以及差别:</p>
<ul>
<li>最大/最小堆只要求父节点比子节点大或者小, 子节点之间没有顺序, 但二叉搜索/排序树(BST)是有顺序的</li>
<li>平衡的二叉搜索树有多种实现如AVL, 红黑树等, 应用如C++ STL中的set/map,</li>
<li>B树, B+树, B*树则是平衡多叉树, 应用如数据库索引</li>
</ul>
<div class="language-go vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">go</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 本例是Golang自带的Example代码, 演示了heap的Init Push Pop Fix操作, 除此之外还有Remove等</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">package</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">container/heap</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fmt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Item</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> struct</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  value    </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  priority </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  index    </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// PriorityQueue 利用数组模拟二叉堆, 并实现heap.Interface所有方法即可</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// heap.Interface是Push(), Pop(), 组合sort.Interface三个接口: Len() Less() Swap()</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// NOTE: 这种Interface的组合也是Golang OOP的核心, 与Java/C#中的继承-多态体系完全不同,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 更像ECMAScript/Typescript的鸭子类型和mixin的思想, 只要符合接口的Shape即可</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(pq) }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Less</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">j</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pq[i].priority </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pq[j].priority</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Swap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">j</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pq[i], pq[j] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pq[j], pq[i]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pq[i].index </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pq[j].index </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> j</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">x</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{}) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  n </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> x.(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item.index </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> n</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> append</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq, item)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Pop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">interface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{} {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  old </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  n </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(old)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> old[n</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item.index </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // for safety</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> old[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : n</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> item</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// update modifies the priority and value of an Item in the queue.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">item</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">priority</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item.value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item.priority </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> priority</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  heap.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Fix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(pq, item.index)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Some items and their priorities.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  items </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "banana"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"apple"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"pear"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pq </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> make</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">PriorityQueue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(items))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value, priority </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> range</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> items {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    pq[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      value:    value,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      priority: priority,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      index:    i,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  heap.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  item </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    value:    </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"orange"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    priority: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  heap.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq, item)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  pq.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(item, item.value, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pq.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    item </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> heap.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Pop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pq).(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    fmt.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%.2d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, item.priority, item.value)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Output: 05:orange 04:pear 03:banana 02:apple</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Typescript入门] 基础开发和调试环境配置]]></title>
            <link>https://code2life.top/blog/0027-typescript-startup</link>
            <guid>https://code2life.top/blog/0027-typescript-startup</guid>
            <pubDate>Tue, 19 Jun 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="typescript入门-基础开发和调试环境配置" tabindex="-1">[Typescript入门] 基础开发和调试环境配置 <a class="header-anchor" href="#typescript入门-基础开发和调试环境配置" aria-label="Permalink to &quot;[Typescript入门] 基础开发和调试环境配置&quot;">&ZeroWidthSpace;</a></h1>
<p>自从换了新工作之后, 有一个多月没更新博客了, 上个月发生了很多事情, 从入职到开始搬砖, 然后一波加班Combo, 到现在终于可以忙里偷闲来写(hua)博(hua)客(shui)了. 由于最近几乎没有时间继续捯饬IoT, 暂时换个话题, 结合一下最近的工作, 开一个Typescript的坑吧.</p>
<blockquote>
<p>2020年更新：这两年TypeScript果然发展非常快，在前端领域尤其是基础库方向已经逐渐占据主流，此篇文章有小部分内容已经过时。</p>
</blockquote>
<h2 id="为什么需要typescript" tabindex="-1">为什么需要Typescript <a class="header-anchor" href="#为什么需要typescript" aria-label="Permalink to &quot;为什么需要Typescript&quot;">&ZeroWidthSpace;</a></h2>
<p>微软在2012年8月公开Typescript之后, 这个Javascript超集语言应该算是小众语言, 一直不温不火, 但随着ECMAScript6的发布, 以及新兴前端技术的出现, 热度在最近两年飙升. 两年前学习ES6的时候, 顺便看了一下Typescript, 那时年少无知, 觉得TS不就是ES6的语法加上啰嗦的类型声明吗, 还不如直接写ES6呢. 这两年搬了一些前端和Nodejs后端的砖后, 尤其是在入职新公司, 在现有的一个Nodejs系统基础上开发一个月, 被杂糅了多种异步调用方式(有async库, 有Promise, 还有无止境的callback hell), 无分层, 无类型, 完全动态(没事delete一个属性, 其他地方再加一个属性), 充满各种无规律无中心的EventEmitter(经常emit一个不知道是啥的字符串)的代码折磨的体无完肤之后, 我才明白TS的深意.</p>
<ol>
<li>类型可强可弱, 能够让代码优雅而又不失灵活性, 感觉有一种动静结合的哲学</li>
<li>更强大的OOP能力, <strong>接口, 继承, 修饰器, 类属性访问控制, 泛型等等</strong>一系列ES6所没有的OOP特性, 在开发大型应用时才体现出的OOP的优势</li>
<li><strong>静态类型和编译检查</strong>, 避免了大量在JS中可能犯的错误</li>
<li><strong>可读性与可维护性</strong>的质变, 能够知悉每一个Promise的resolve类型, 每个枚举字符串可能的值. 这些在ES中只能通过jsdoc去写在注释里, 而且无法预知运行时真正的类型的对象, 而用TS后一切尽在代码中, 当你阅读代码时, 发现类型声明像是救命稻草.</li>
<li>大部分第三方库已经提供了类型声明文件(.d.ts), IDE自动补全与即时文档带来的开发效率提升远远大于多写一些类型声明的花费的时间<br>
...</li>
</ol>
<p>说了这么多好处, 但迄今为止, 笔者还没有真正用TS开发过, 这篇记录下TS的入门之旅.</p>
<h2 id="搭建一个typescript项目" tabindex="-1">搭建一个Typescript项目 <a class="header-anchor" href="#搭建一个typescript项目" aria-label="Permalink to &quot;搭建一个Typescript项目&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="step1-基础配置文件" tabindex="-1">Step1. 基础配置文件 <a class="header-anchor" href="#step1-基础配置文件" aria-label="Permalink to &quot;Step1. 基础配置文件&quot;">&ZeroWidthSpace;</a></h4>
<p>玩转TS只需要一个nodejs环境就可以了, 或许几年后<a href="https://github.com/ry/deno" target="_blank" rel="noreferrer">Deno</a>会替代nodejs, 但是目前tsc tslint等都还是基于nodejs的, 首先npm init新建一个项目, 初始化好package.json, 下面是一些<strong>必要的devDependencies</strong></p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"devDependencies"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "@types/node"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"^10.1.2"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "ts-node"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"^6.1.0"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "tslint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"^5.10.0"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "typescript"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"^2.9.1"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>然后给项目添加上<strong>tsconfig.json</strong>和<strong>tslint.json</strong>文件,</p>
<ul>
<li><strong>tsconfig.json</strong>: 配置TS项目的基础信息, 比如代码路径, 编译选项等等. 官方文档<a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html" target="_blank" rel="noreferrer">在这里</a>和<a href="https://www.typescriptlang.org/docs/handbook/compiler-options.html" target="_blank" rel="noreferrer">这里</a></li>
<li><strong>tslint.json</strong>: 类似jslint/eslint, 用于TS的代码风格检查.</li>
</ul>
<p>下面是我自己使用的配置, 这样代码需要写在src, 并会被编译成ES6语法的JS和sourceMap到dist目录中等等, 这里tslint配置的个人比较喜欢的代码风格.
tsconfig.json</p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "compilerOptions"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "module"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"commonjs"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "esModuleInterop"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "target"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"es6"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "noImplicitAny"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "moduleResolution"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"node"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "sourceMap"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "outDir"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dist"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "allowSyntheticDefaultImports"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "baseUrl"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "paths"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">            "*"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                "node_modules/*"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                "src/types/*"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        },</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //"allowJs": true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "include"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "src/**/*"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><p>tslint.json</p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "rules"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "class-name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "comment-format"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-space"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "indent"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "spaces"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "one-line"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-open-brace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-whitespace"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "no-var-keyword"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "quotemark"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "single"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "avoid-escape"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "semicolon"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "always"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "ignore-bound-class-methods"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "whitespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-branch"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-decl"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-operator"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-module"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-separator"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      "check-type"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "typedef-whitespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "call-signature"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"nospace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "index-signature"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"nospace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "parameter"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"nospace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "property-declaration"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"nospace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "variable-declaration"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"nospace"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "call-signature"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"onespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "index-signature"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"onespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "parameter"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"onespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "property-declaration"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"onespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "variable-declaration"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"onespace"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "no-internal-module"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "no-trailing-whitespace"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "no-null-keyword"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "prefer-const"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "jsdoc-format"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br></div></div><h4 id="step2-写一个ts文件编译运行" tabindex="-1">Step2. 写一个TS文件编译运行 <a class="header-anchor" href="#step2-写一个ts文件编译运行" aria-label="Permalink to &quot;Step2. 写一个TS文件编译运行&quot;">&ZeroWidthSpace;</a></h4>
<p>创建src目录并编写一个.ts文件放进去, 比如一个main.ts, 里面可以写一些TS的代码, 然后在package.json中定义一些scripts命令即可. 比如这样:</p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tslint -p tsconfig.json &#x26;&#x26; tsc -p tsconfig.json"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">},</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>让一个TS项目运行起来就是如此简单, 如果编译选项打开allowJS(不建议打开), 甚至可以像写JS一样去写TS. 但真正的项目还需要一些必要的元素, 比如<strong>实时调试, 单元测试, 构建打包</strong>等等. 下面分别介绍.</p>
<h2 id="添加单元测试配置" tabindex="-1">添加单元测试配置 <a class="header-anchor" href="#添加单元测试配置" aria-label="Permalink to &quot;添加单元测试配置&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="mocha" tabindex="-1">Mocha <a class="header-anchor" href="#mocha" aria-label="Permalink to &quot;Mocha&quot;">&ZeroWidthSpace;</a></h4>
<p>如果是用的<a href="https://www.npmjs.com/package/mocha" target="_blank" rel="noreferrer">mocha</a>作为单元测试框架配合其他断言库(should.js chai等等), 只需要指定特定require的程序即可, 即在mocha的命令行参数添加**--require ts-node/register**, 具体可以参考<a href="https://github.com/ReactiveX/rxjs" target="_blank" rel="noreferrer">rxjs</a>的测试脚本配置, 也有nyc进行覆盖率测试的例子</p>
<h4 id="jest" tabindex="-1">Jest <a class="header-anchor" href="#jest" aria-label="Permalink to &quot;Jest&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>但是</strong>个人感觉现在<a href="https://github.com/facebook/jest" target="_blank" rel="noreferrer">jest</a>似乎比mocha更受欢迎, 配置更简单, 而且像<strong>Mock, 覆盖测试, 断言库</strong>等特性都集于一体, 使用更方便, 添加jest需要以下几个步骤</p>
<h4 id="step1-添加jest-config-js" tabindex="-1">Step1. 添加jest.config.js <a class="header-anchor" href="#step1-添加jest-config-js" aria-label="Permalink to &quot;Step1. 添加jest.config.js&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">exports</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  globals: {</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'ts-jest'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      tsConfigFile: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./tsconfig.json'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  moduleFileExtensions: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    'js'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  transform: {</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    '^.+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\\</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">.(ts|tsx)$'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./node_modules/ts-jest/preprocessor.js'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  testMatch: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    '**/test/**/*.test.(ts|js)'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  testEnvironment: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'node'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h4 id="step2-添加相关依赖库到devdependencies" tabindex="-1">Step2. 添加相关依赖库到devDependencies <a class="header-anchor" href="#step2-添加相关依赖库到devdependencies" aria-label="Permalink to &quot;Step2. 添加相关依赖库到devDependencies&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> jest</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @types/jest</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ts-jest</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --save-dev</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h4 id="step3-编写一个测试用例并执行" tabindex="-1">Step3. 编写一个测试用例并执行 <a class="header-anchor" href="#step3-编写一个测试用例并执行" aria-label="Permalink to &quot;Step3. 编写一个测试用例并执行&quot;">&ZeroWidthSpace;</a></h4>
<p>首先在test目录下创建一个.test.ts结尾的文件, 然后编写测试用例</p>
<div class="language-typescript vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">describe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'test 1'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'always pass'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toBe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>然后执行jest命令即可, 如果是非全局安装jest, 则需要执行npx jest, 另外一个办法是写入package.json的scripts属性中, 然后直接npm test. 添加test后的package.json片段是这样的, 还可以顺便在coverage目录下生成了一个覆盖率测试报告:</p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tslint -p tsconfig.json &#x26;&#x26; tsc -p tsconfig.json"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"jest --coverage --verbose"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">},</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h2 id="webpack打包配置" tabindex="-1">Webpack打包配置 <a class="header-anchor" href="#webpack打包配置" aria-label="Permalink to &quot;Webpack打包配置&quot;">&ZeroWidthSpace;</a></h2>
<p>到现在为止, 我们已经有了项目结构, 源文件, 编译配置, 单元测试的雏形, 如果是前端项目或者有代码混淆要求的后端项目, 还需要打包发布. 正好最近Webpack4也出来有一段时间了, 来体验一下号称零配置的Webpack4吧</p>
<h4 id="step1-准备好必要的devdependencies" tabindex="-1">Step1. 准备好必要的devDependencies <a class="header-anchor" href="#step1-准备好必要的devdependencies" aria-label="Permalink to &quot;Step1. 准备好必要的devDependencies&quot;">&ZeroWidthSpace;</a></h4>
<p>其中webpack webpack-cli ts-loader是必须的, 其他的webpack插件自行选配, clean和bundle analyzer比较实用, 像CommonChunk Uglify等Plugin已经<strong>无需再单独配置</strong>了, Webpack4有很多的优化, 具体不再赘述, <a href="https://segmentfault.com/a/1190000014247030" target="_blank" rel="noreferrer">这里</a>有一个简要的总结</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> webpack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> webpack-cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ts-loader</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --save-dev</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> clean-webpack-plugin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> webpack-bundle-analyzer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --save-dev</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h4 id="step2-添加webpack-config-js" tabindex="-1">Step2. 添加webpack.config.js <a class="header-anchor" href="#step2-添加webpack-config-js" aria-label="Permalink to &quot;Step2. 添加webpack.config.js&quot;">&ZeroWidthSpace;</a></h4>
<p>这是一个后端typescript项目的简单示例, 如果是前端项目, target需要修改, 还需要一些html css相关的加载和处理配置, 以及vue react库相关的.vue和.jsx的loader等等, 网上教程很多, Webpack官网的文档也很详细, <a href="https://webpack.docschina.org/configuration/" target="_blank" rel="noreferrer">这里</a>是一个翻译后的文档</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> path</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'path'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> webpack</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'webpack'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> CleanWebpackPlugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'clean-webpack-plugin'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> BundleAnalyzerPlugin</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'webpack-bundle-analyzer'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).BundleAnalyzerPlugin;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">exports</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  entry: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./src/main.ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  devtool: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'source-map'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  target: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'node'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  mode: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'production'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  plugins: [</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> CleanWebpackPlugin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./dist/public'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 这些plugin是可选的, 也可以添加DefinePlugin或者第三方Plugin等等</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // BundleAnalyzerPlugin可视化模块依赖, 非常好用</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BundleAnalyzerPlugin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      analyzerMode: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'static'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  module: {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 划重点, 定义一个ts的loader即可打包typescript</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    rules: [{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      test:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">\.</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">ts</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      use: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        'ts-loader'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  resolve: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    extensions: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'.ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'.node'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  optimization : {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // Webpack4 production模式下很多opimization自动开启, 无需声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    minimize: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    nodeEnv: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'production'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  output: {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 最后输出到dist/public下, 不会与tsc的输出冲突</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    filename: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'[name].[hash].js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    path: path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(__dirname, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./dist/public'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br></div></div><h4 id="step3-开始打包" tabindex="-1">Step3. 开始打包 <a class="header-anchor" href="#step3-开始打包" aria-label="Permalink to &quot;Step3. 开始打包&quot;">&ZeroWidthSpace;</a></h4>
<p>编写好webpack.config.js后, 再在package.json中定义一些构建的scripts即可, 根据项目的不同可能需要把不同环境的webpack.config分开, 并且根据开发还是生产环境执行不同的webpack命令, 但<strong>如果是typescript后端项目</strong>, <strong>webpack作为production环境</strong>, <strong>tsc作为开发环境</strong>已经可以满足需要了. 至此, package.json已经有这些命令了:</p>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tslint -p tsconfig.json &#x26;&#x26; tsc -p tsconfig.json"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build_prod"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tslint -p tsconfig.json &#x26;&#x26; webpack --config webpack.config.js --progress --color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "lint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tslint -p tsconfig.json"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"jest --coverage --verbose"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>执行npm run build_prod 即可打包成一个很小的混淆后的js文件了.</p>
<h2 id="调试typescript" tabindex="-1">调试Typescript <a class="header-anchor" href="#调试typescript" aria-label="Permalink to &quot;调试Typescript&quot;">&ZeroWidthSpace;</a></h2>
<p>由于Typescript是JS的超集, 而且能编译成JS, 所以JS的调试方法对TS一样有效, 如果是前端项目, 浏览器中直接在sourceMap后的代码打断点就可以了. 后端项目可以用node --inspect或者node --inspect-brk去打断点调试编译后的JS代码. 如果运行不在本地, 访问chrome://inspect, 配置远程debug端口在chrome中调试也可以, WebStorm也自带远程调试的配置. 总之, JS的调试方法对TS同样适用, 但此处分享一下VS Code中更简单的调试配置(launch.json), 一键Debug源代码和单元测试.</p>
<ul>
<li>对于固定入口的node程序, 在<strong>runtimeArgs指定 --require</strong>使用的第三方库即可实现即时调试</li>
<li>对于单元测试, 用<strong>inspect</strong>链接到node进程中调试即可, 但需要指定测试框架对应的启动脚本, 比如jest.js</li>
</ul>
<div class="language-json vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "version"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0.2.0"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "configurations"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"node"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "request"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"launch"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Debug"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "runtimeArgs"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">          "-r"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">          "ts-node/register"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "args"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "src/main.ts"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "cwd"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${workspaceFolder}"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"node"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "request"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"launch"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Debug Unit Test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "protocol"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"inspector"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "program"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${workspaceFolder}/node_modules/jest/bin/jest.js"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "args"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "main.test.ts"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      ],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "cwd"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${workspaceFolder}"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br></div></div><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文只是简单介绍的从0开始搭建typescript的基础开发调试环境, 对于项目开发是远远不够的. 具体项目至少还需要考虑日志, CI/CD配置, 还有一些涉及前端或后端代码细节需要考虑的比如封装Ajax, 统一的枚举和常量, 代码逻辑分层, 公共组件的封装, 状态, 会话, 路由, 鉴权等等.<br>
不管做什么, 这些基础配置都是类似的, 虽然简单, 但从中也能学到一些东西, Typescript的前途至少目前来看还是非常明朗的. 最近也在用TS做一些尝试, 等以后经验多一些再去总结更深入的TS使用体验吧</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Geek之路] 自己动手做智能家居(二)智能门窗感应器的开发]]></title>
            <link>https://code2life.top/blog/0026-iot-em-dev</link>
            <guid>https://code2life.top/blog/0026-iot-em-dev</guid>
            <pubDate>Wed, 25 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="geek之路-自己动手做智能家居-二-智能门窗感应器的开发" tabindex="-1">[Geek之路] 自己动手做智能家居(二)智能门窗感应器的开发 <a class="header-anchor" href="#geek之路-自己动手做智能家居-二-智能门窗感应器的开发" aria-label="Permalink to &quot;[Geek之路] 自己动手做智能家居(二)智能门窗感应器的开发&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>2020年8月更新: 这两年对嵌入式方向的学习太少了，回过头看两年前做的东西，虽然模块连到一起通过一些简单的代码能跑起来，但没有产品化的思维，不具有实用价值。智能家居有Home Assistant这么强大的轮子，作为一个不专业人士从头自己做太不划算了，后面还会继续学习这方面的知识，慢慢摸索。但应该要有一些产品思维，把东西做成能用的，而不仅是简单能跑的。</p>
</blockquote>
<p>上一篇<a href="/blog/0025-iot-design.html">架构设计与技术选型</a>中, 已经完成了大致的软硬件框架设计, 这节开始记录嵌入式开发的过程.</p>
<h2 id="模块引脚与电路接线" tabindex="-1">模块引脚与电路接线 <a class="header-anchor" href="#模块引脚与电路接线" aria-label="Permalink to &quot;模块引脚与电路接线&quot;">&ZeroWidthSpace;</a></h2>
<p>首先嵌入式设备这块有4个硬件:</p>
<ul>
<li><strong>Wifi模块</strong>: 承担数据处理和收发的重任, 整个门窗感应器设备的<strong>核心</strong>(<strong>ESP8266-01S只要9块8, 超强Wifi模块带回家</strong>)</li>
<li><strong>干簧管模块</strong>: 经过模转数封装的干簧管传感器, Digital Output直接输入TTL电平. 磁铁距离约<strong>1.5cm以内时输出低电平</strong>, 磁铁距离<strong>超过阈值输出高电平</strong>(<strong>只要2块钱一个, 配上一个3毛钱的钕铁硼磁铁就开袋即食</strong>)</li>
<li><strong>蜂鸣器模块</strong>: 当干簧管模块输出高电平时, 即门被打开时, 中间那个引脚应该<strong>输入PWM波</strong>让蜂鸣器鸣叫(买的是3引脚无源蜂鸣器, <strong>只要8毛钱一个</strong>)</li>
</ul>
<p>电路仿真软件Proteus里面没有Wifi模块, 也找不到3脚蜂鸣器, 这里拿纸笔画了个很不专业的线路图, 包括了每个模块的引脚接线方法. 其中ESP8266-01S有两个GPIO引脚, 作用如下:</p>
<ul>
<li>GPIO0用作系统的输入, 感受来自干簧管传感器的电平</li>
<li>GPIO2用作系统的输出, 控制输出PWM波来让蜂鸣器沉默或发出不同的声音(更正一下: GPIO2应该连接三脚蜂鸣器的S端, 中间的引脚连接5V电源, 这里笔误了)
<img src="//filecdn.code2life.top/iot-door-sensor-ct.jpg" alt="circuit"></li>
</ul>
<h2 id="固件烧录" tabindex="-1">固件烧录 <a class="header-anchor" href="#固件烧录" aria-label="Permalink to &quot;固件烧录&quot;">&ZeroWidthSpace;</a></h2>
<p>ESP8266-01S这款神奇的元件, 8Mb的Flash, 最高160MHz的32位CPU, 功耗最低20μA, IEEE 802.11b/g/n都支持 ,甚至还能当路由器, 竟然只要<strong>10块钱不到</strong>. 文档在<a href="//wiki.ai-thinker.com/esp8266" target="_blank" rel="noreferrer">这里</a>都能查到, 非常齐全. 相关的工具和SDK在<a href="//wiki.ai-thinker.com/tools" target="_blank" rel="noreferrer">这里</a>都能下载到最新版本. 之前只烧录过Arduino的AVR单片机和51单片机, 这块神奇的板子还没试过自己写固件烧进去, 于是先依葫芦画瓢烧一个HelloWorld试试.</p>
<h4 id="环境准备" tabindex="-1">环境准备 <a class="header-anchor" href="#环境准备" aria-label="Permalink to &quot;环境准备&quot;">&ZeroWidthSpace;</a></h4>
<p>第一步是通过UART连接串口到电脑上, 原料: <strong>USB转TTL模块</strong>一只(<strong>最好是FT232RL的芯片</strong>, 据说更稳定), 杜邦线若干. 接线如下:</p>
<ul>
<li>ESP8266 RXD -&gt; USB转串口模块 TXD</li>
<li>ESP8266 TXD -&gt; USB转串口模块 RXD</li>
<li>ESP8266 GND USB转串口模块 GND -&gt; 共地</li>
<li>ESP8266 VCC -&gt; 3.3V电源</li>
<li>USB转串口模块 USB -&gt; 计算机USB</li>
</ul>
<p>这个时候用串口软件, 设置为<strong>74880</strong>的波特率, 模块上电就能看到串口输出了. 一般使用的<strong>115200</strong>波特率会导致串口看到的启动信息是乱码(貌似和ESP板子里的26M晶振有关), 115200波特率看到的启动信息大概是这样的， 这时可以给串口发送AT指令连接Wifi或是建立无线热点等等
<img src="//filecdn.code2life.top/8266-serial-1.jpg" alt="serial"></p>
<p><strong>如果把GPIO0接地拉低, 重新上电或复位就能看到上图最后一行那样的乱码, 这样是进入了下载模式</strong>, 后面烧录固件的时候, 都是需要把<strong>GPIO0拉低再上电复位的</strong>.</p>
<h4 id="工程编译和程序烧录" tabindex="-1">工程编译和程序烧录 <a class="header-anchor" href="#工程编译和程序烧录" aria-label="Permalink to &quot;工程编译和程序烧录&quot;">&ZeroWidthSpace;</a></h4>
<p>从官网下载的一体化SDK就包括了默认的很多示例项目, 工程目录的结构在下图中用红字标注了. SDK有<strong>NonOS和RTOS</strong>两种, 一般选择NonOS即可, RTOS则类似于μcOSII这样的根据时间片和任务优先级调度多任务的<strong>实时操作系统</strong>固件, 这个简单的应用还用不到.
<img src="//filecdn.code2life.top/iot-project-esp8266-1.jpg" alt="ec"></p>
<p>先把HelloWorld工程执行<strong>Clean Project, Build Project</strong>, 编译成功后开烧. 期间也经历过一些波折, 用了技术交流群里的老版本烧录工具, 报了一些莫名奇妙的错误. 最后还是在官网找的资料和工具靠谱, 烧录成功了. 配置如下图所示, 亦可<strong>合并</strong>成一个大的hex直接从0x00烧进去.<br>
<img src="//filecdn.code2life.top/8266-flash-1.jpg" alt="sl"></p>
<h2 id="程序编写" tabindex="-1">程序编写 <a class="header-anchor" href="#程序编写" aria-label="Permalink to &quot;程序编写&quot;">&ZeroWidthSpace;</a></h2>
<p>基础环境和编译烧录都打通了, 开始撸起袖子写代码了. SDK提供的示例非常丰富, 我选择<strong>在mqtt示例的基础上开发</strong>. 相关代码放到Github中了, <a href="https://github.com/Code2Life/HarmonyIOT" target="_blank" rel="noreferrer">地址是这里</a>, 在官网工具页面上下载安装好一体化SDK并把mqtt示例的项目替换成Github此目录下的Eclipse项目即可.</p>
<p>整体的设计逻辑是这样的:</p>
<ol>
<li><strong>重写MQTT连接成功以及订阅事件的回调函数</strong></li>
</ol>
<ul>
<li>连接上MQTT Broker时, 发送设备上线消息</li>
<li>订阅设备开关命令, 并根据开关调用蜂鸣器的开关函数</li>
</ul>
<ol start="2">
<li><strong>实现蜂鸣器开关函数</strong></li>
</ol>
<ul>
<li>蜂鸣器关, GPIO2的PWM波占空比设置为0</li>
<li>蜂鸣器开, GPIO2的PWM波占空比设置为100</li>
</ul>
<ol start="3">
<li><strong>设置一个定时器, 每隔500ms检测一次干簧管传感器的电平</strong></li>
</ol>
<ul>
<li>低电平即门正常关闭状态: 重置一些状态变量</li>
<li>高电平即门打开状态: 调用蜂鸣器开函数, 如果鸣叫超过1分钟不做处理</li>
</ul>
<ol start="4">
<li><strong>定时器每运行60次即30秒, 向MQTT Broker发布一条设备在线消息</strong></li>
</ol>
<p>下面贴一下user_main.c中的关键代码, 基本上完全的面向过程编程</p>
<div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "ets_sys.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "driver/uart.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "osapi.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "mqtt.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "wifi.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "pwm.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "config.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "debug.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "gpio.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "user_interface.h"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "mem.h"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> INTERVAL</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 500</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> INIT_DELAY</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10000</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> HEART_BEAT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 60</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> //30s</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BEEP_DURATION</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 120</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> //60s</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PWM_PERIOD</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 500</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> //500ns</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PWM_DUTY</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#define</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PWM_IDLE</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//传感器检测标识, 每次触发定时器自增长</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">LOCAL </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint64_t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> id </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">LOCAL </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> beeping </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">LOCAL </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> beepTimes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">LOCAL </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">MQTT_Client mqttClient;</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">os_timer_t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sensor_timer;</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">os_timer_t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> beep_timer;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//生成PWM波的GPIO2</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">uint32 io_info</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">[]</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {{PERIPHS_IO_MUX_GPIO2_U,FUNC_GPIO2,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}};</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//PWM波占空比</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">uint32 </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">pwm_duty_init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {PWM_IDLE};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/***</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 控制无源蜂鸣器打开和关闭</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 在GPIO2上生成PWM, GPIO模拟PWM</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> beep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">bool</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> isBeep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  beeping </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isBeep;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isBeep) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(beepTimes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> BEEP_DURATION) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"PWM -> Beep </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      pwm_set_duty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(PWM_DUTY, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      pwm_start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"PWM -> Stop Beep </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      pwm_set_duty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(PWM_IDLE, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      pwm_start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"PWM 0 -> No Beep </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    pwm_set_duty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(PWM_IDLE, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    pwm_start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 获取数据以及处理逻辑</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 1. 获取GPIO0电平</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 2. 高电平触发蜂鸣器</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 3. mqtt发送数据到服务器</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 4. 每60次检测(30s), 触发一次心跳健康检测</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> handleSensor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_disarm</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //设备心跳检测</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(id </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> HEART_BEAT </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    MQTT_Publish</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/heartbeat"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DOOR_SENSOR"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">11</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //获取GPIO0输入并判断</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">   uint8 sensor_res </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> GPIO_INPUT_GET</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GPIO_ID_PIN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //门被打开(干簧管无磁)的状态</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sensor_res </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    beepTimes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(beeping </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Door Opened!...</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      beep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      MQTT_Publish</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/data"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DOOR_SENSOR::door_open"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">11</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //若门开通过远程控制不要叫, 关门后1分钟, 远程控制失效</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //若门关状态远程控制报警, 1分钟后也失效回归平静</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(remoteControl) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      beepTimes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(beepTimes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">>=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> BEEP_DURATION) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      beepTimes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(beeping </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Door Closed!...</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      beep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      MQTT_Publish</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/data"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DOOR_SENSOR::door_close"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">11</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_setfn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer, (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">os_timer_func_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)handleSensor, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_arm</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer, INTERVAL, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 干簧管传感器数据获取定时器, 500ms触发一次</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> initSensor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  PIN_FUNC_SELECT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Sensor READY</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //初次启动等待wifi和mqtt初始化完毕再检测传感器</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_disarm</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_setfn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer, (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">os_timer_func_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)handleSensor, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_timer_arm</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sensor_timer, INIT_DELAY, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> wifiConnectCb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint8_t</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> STATION_GOT_IP){</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    MQTT_Connect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    MQTT_Disconnect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> mqttConnectedCb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)args;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MQTT: Connected</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //发布设备上线注册消息</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_Publish</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(client, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/online"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"DOOR_SENSOR"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">11</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //订阅开关命令</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_Subscribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(client, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/on"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_Subscribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(client, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/off"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> mqttDisconnectedCb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)args;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MQTT: Disconnected</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> mqttPublishedCb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)args;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MQTT: Published</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> mqttDataCb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> char*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> topic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> topic_len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> char</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">uint32_t</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> data_len</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  char</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">topicBuf </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">char*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">os_zalloc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(topic_len</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dataBuf </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">char*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">os_zalloc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(data_len</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (MQTT_Client</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)args;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_memcpy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(topicBuf, topic, topic_len);</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  topicBuf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[topic_len] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_memcpy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(dataBuf, data, data_len);</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  dataBuf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[data_len] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Receive topic: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">, data: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, topicBuf, dataBuf);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //订阅数据的回调, 用于远程控制</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">os_strncmp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(topicBuf, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/on"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Remote control -> on. </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    beep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">os_strncmp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(topicBuf, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/device/off"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">11</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Remote control -> off. </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    remoteControl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    beep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_free</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(topicBuf);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_free</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(dataBuf);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/******************************************************************************</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * FunctionName : user_rf_cal_sector_set</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * Description  : SDK just reversed 4 sectors, used for rf init data and paramters.</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                We add this function to force users to set rf cal sector, since</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                we don't know which sector is free in user's application.</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                sector map for last several sectors : ABCCC</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                A : rf cal</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                B : rf init data</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *                C : sdk parameters</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * Parameters   : none</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * Returns      : rf cal sector</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> *******************************************************************************/</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">uint32 ICACHE_FLASH_ATTR</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">user_rf_cal_sector_set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    enum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> flash_size_map size_map </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> system_get_flash_size_map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    uint32 rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    switch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (size_map) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_4M_MAP_256_256:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 128</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            break</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_8M_MAP_512_512:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 256</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            break</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_16M_MAP_512_512:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_16M_MAP_1024_1024:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 512</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            break</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_32M_MAP_512_512:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        case</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> FLASH_SIZE_32M_MAP_1024_1024:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            break</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            rf_cal_sec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            break</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> rf_cal_sec;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> user_init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  uart_init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(BIT_RATE_115200, BIT_RATE_115200);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_delay_us</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  CFG_Load</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_InitConnection</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.security);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_InitClient</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, sysCfg.device_id, sysCfg.mqtt_user, sysCfg.mqtt_pass, sysCfg.mqtt_keepalive, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_InitLWT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/lwt"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"offline"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_OnConnected</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, mqttConnectedCb);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_OnDisconnected</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, mqttDisconnectedCb);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_OnPublished</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, mqttPublishedCb);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  MQTT_OnData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">mqttClient, mqttDataCb);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  WIFI_Connect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">System started ...</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //初始化PWM</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  pwm_init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(PWM_PERIOD,  pwm_duty_init , </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ,io_info);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  set_pwm_debug_en</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  pwm_start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  os_printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"PWM START</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\r\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //初始化MQTT client, 发布设备注册消息, 订阅设备开关命令消息及回调</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //GPIO0 监听干簧管电位, 高电位触发GPIO2</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //GPIO2 生成PWM激活蜂鸣器</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //MQTT client 发布警告消息通知MQTT broker</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  initSensor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br><span class="line-number">107</span><br><span class="line-number">108</span><br><span class="line-number">109</span><br><span class="line-number">110</span><br><span class="line-number">111</span><br><span class="line-number">112</span><br><span class="line-number">113</span><br><span class="line-number">114</span><br><span class="line-number">115</span><br><span class="line-number">116</span><br><span class="line-number">117</span><br><span class="line-number">118</span><br><span class="line-number">119</span><br><span class="line-number">120</span><br><span class="line-number">121</span><br><span class="line-number">122</span><br><span class="line-number">123</span><br><span class="line-number">124</span><br><span class="line-number">125</span><br><span class="line-number">126</span><br><span class="line-number">127</span><br><span class="line-number">128</span><br><span class="line-number">129</span><br><span class="line-number">130</span><br><span class="line-number">131</span><br><span class="line-number">132</span><br><span class="line-number">133</span><br><span class="line-number">134</span><br><span class="line-number">135</span><br><span class="line-number">136</span><br><span class="line-number">137</span><br><span class="line-number">138</span><br><span class="line-number">139</span><br><span class="line-number">140</span><br><span class="line-number">141</span><br><span class="line-number">142</span><br><span class="line-number">143</span><br><span class="line-number">144</span><br><span class="line-number">145</span><br><span class="line-number">146</span><br><span class="line-number">147</span><br><span class="line-number">148</span><br><span class="line-number">149</span><br><span class="line-number">150</span><br><span class="line-number">151</span><br><span class="line-number">152</span><br><span class="line-number">153</span><br><span class="line-number">154</span><br><span class="line-number">155</span><br><span class="line-number">156</span><br><span class="line-number">157</span><br><span class="line-number">158</span><br><span class="line-number">159</span><br><span class="line-number">160</span><br><span class="line-number">161</span><br><span class="line-number">162</span><br><span class="line-number">163</span><br><span class="line-number">164</span><br><span class="line-number">165</span><br><span class="line-number">166</span><br><span class="line-number">167</span><br><span class="line-number">168</span><br><span class="line-number">169</span><br><span class="line-number">170</span><br><span class="line-number">171</span><br><span class="line-number">172</span><br><span class="line-number">173</span><br><span class="line-number">174</span><br><span class="line-number">175</span><br><span class="line-number">176</span><br><span class="line-number">177</span><br><span class="line-number">178</span><br><span class="line-number">179</span><br><span class="line-number">180</span><br><span class="line-number">181</span><br><span class="line-number">182</span><br><span class="line-number">183</span><br><span class="line-number">184</span><br><span class="line-number">185</span><br><span class="line-number">186</span><br><span class="line-number">187</span><br><span class="line-number">188</span><br><span class="line-number">189</span><br><span class="line-number">190</span><br><span class="line-number">191</span><br><span class="line-number">192</span><br><span class="line-number">193</span><br><span class="line-number">194</span><br><span class="line-number">195</span><br><span class="line-number">196</span><br><span class="line-number">197</span><br><span class="line-number">198</span><br><span class="line-number">199</span><br><span class="line-number">200</span><br><span class="line-number">201</span><br><span class="line-number">202</span><br><span class="line-number">203</span><br><span class="line-number">204</span><br><span class="line-number">205</span><br><span class="line-number">206</span><br><span class="line-number">207</span><br><span class="line-number">208</span><br><span class="line-number">209</span><br><span class="line-number">210</span><br><span class="line-number">211</span><br><span class="line-number">212</span><br><span class="line-number">213</span><br><span class="line-number">214</span><br><span class="line-number">215</span><br><span class="line-number">216</span><br><span class="line-number">217</span><br><span class="line-number">218</span><br><span class="line-number">219</span><br><span class="line-number">220</span><br><span class="line-number">221</span><br><span class="line-number">222</span><br><span class="line-number">223</span><br><span class="line-number">224</span><br><span class="line-number">225</span><br><span class="line-number">226</span><br><span class="line-number">227</span><br><span class="line-number">228</span><br><span class="line-number">229</span><br><span class="line-number">230</span><br><span class="line-number">231</span><br><span class="line-number">232</span><br><span class="line-number">233</span><br><span class="line-number">234</span><br><span class="line-number">235</span><br><span class="line-number">236</span><br><span class="line-number">237</span><br><span class="line-number">238</span><br><span class="line-number">239</span><br><span class="line-number">240</span><br><span class="line-number">241</span><br><span class="line-number">242</span><br><span class="line-number">243</span><br><span class="line-number">244</span><br><span class="line-number">245</span><br><span class="line-number">246</span><br><span class="line-number">247</span><br><span class="line-number">248</span><br><span class="line-number">249</span><br><span class="line-number">250</span><br><span class="line-number">251</span><br><span class="line-number">252</span><br><span class="line-number">253</span><br><span class="line-number">254</span><br><span class="line-number">255</span><br><span class="line-number">256</span><br><span class="line-number">257</span><br><span class="line-number">258</span><br><span class="line-number">259</span><br><span class="line-number">260</span><br><span class="line-number">261</span><br><span class="line-number">262</span><br><span class="line-number">263</span><br><span class="line-number">264</span><br><span class="line-number">265</span><br><span class="line-number">266</span><br><span class="line-number">267</span><br></div></div><p>这个代码是调试过的版本了, 按照设计的接线和这些代码编译的固件, 实现了预期功能. <strong>磁铁一碰上就会滴滴滴~的叫, 远程一条MQTT消息就可以控制蜂鸣器</strong>, 挺有意思的.</p>
<p>好久没写C语言了, 写起来感觉距离机器很近. <strong>一次又一次接线、烧录、调试</strong>结束了, 还因为VCC-GND接反报废了一只干簧管, 终于完成嵌入式的部分了, 下节准备记录下后端开发.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Geek之路] 自己动手做智能家居(一)架构设计与技术选型]]></title>
            <link>https://code2life.top/blog/0025-iot-design</link>
            <guid>https://code2life.top/blog/0025-iot-design</guid>
            <pubDate>Mon, 23 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="geek之路-自己动手做智能家居-一-架构设计与技术选型" tabindex="-1">[Geek之路] 自己动手做智能家居(一)架构设计与技术选型 <a class="header-anchor" href="#geek之路-自己动手做智能家居-一-架构设计与技术选型" aria-label="Permalink to &quot;[Geek之路] 自己动手做智能家居(一)架构设计与技术选型&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="背景和需求" tabindex="-1">背景和需求 <a class="header-anchor" href="#背景和需求" aria-label="Permalink to &quot;背景和需求&quot;">&ZeroWidthSpace;</a></h2>
<p>前几年<strong>物联网</strong>刚刚兴起的时候就对这个行业很有兴趣, 去年买过一个树莓派3B捯饬过一段时间, 也了解了一些关于硬件方面的基础知识. 如今5G网络、智能家居、可穿戴设备、VR/AR/MR也都有了长足的发展, 再有如今机器学习领域的加持, 个人认为这些都是未来最有前景的方向.</p>
<p>作为一个软件工程专业出身的纯软件码农, 物联网用到的很多嵌入式和硬件相关的技术涉足很少, 先从简单的开始, 一步一个脚印学习吧. 现在有一个需求场景: 最近出门有时候忘记关门, 想做一个<strong>自动检测门是否关闭并可以远程控制的设备</strong>, 不仅能够提醒我这个粗心的人关门, 还可以作为家庭安防的一个组件. 米家也有类似的产品, 比如<a href="https://youpin.mi.com/detail?gid=409" target="_blank" rel="noreferrer">这个</a>.
<img src="//filecdn.code2life.top/door-sensor.jpg" alt="door"></p>
<h2 id="架构设计" tabindex="-1">架构设计 <a class="header-anchor" href="#架构设计" aria-label="Permalink to &quot;架构设计&quot;">&ZeroWidthSpace;</a></h2>
<p>虽然现在做的是一个简单的门窗传感器, 但考虑到后面会<strong>扩展其他设备</strong>, 良好的架构非常重要. 之前做的详细设计比较多, 架构设计也是边学边做. 首先系统的基本组件有以下几个, 通过分析逐渐细化, 来得到系统的<strong>模块视图和部署视图</strong>.</p>
<ul>
<li><strong>智能设备</strong>: 像门窗传感器这样的硬件设备</li>
<li><strong>移动端APP</strong>: 能够实时获取到智能设备的信息并进行控制管理</li>
<li><strong>云端服务</strong>: 设备和移动端后台服务以及数据中心</li>
<li>局域网服务: 房屋内部无线网络的小中心, 比如智能音箱, 家庭机器人这样的设备, 计算能力很强, 能够在房屋内进行设备的控制管理及更高级的服务, 暂时还没有计划做这块.</li>
</ul>
<h4 id="质量属性分析" tabindex="-1">质量属性分析 <a class="header-anchor" href="#质量属性分析" aria-label="Permalink to &quot;质量属性分析&quot;">&ZeroWidthSpace;</a></h4>
<p>通过需求明确了门窗传感器的应用场景和功能需求, 可以根据这些应用场景中所需要的非功能需求去确定系统的质量属性, 下面一一分析各项质量属性, 再根据需要实现的<strong>质量属性优先级</strong>作为架构设计的参考.</p>
<ul>
<li><strong>可用性</strong>: 目前仅家庭内部使用, 可用性达到99%以上即可.</li>
<li><strong>可扩展性</strong>: 后续扩展其他设备, 要求系统能够支持动态扩展硬件设备</li>
<li><strong>性能</strong>: 系统对实时性有一定要求, 传感器检测到变化需500ms内响应; 远程获取数据或控制时, 服务器延时在100ms以内; APP或管理端页面的响应时间在500ms以内</li>
<li><strong>安全性</strong>: 由于数据会通过服务器交互, 需要有基本的用户鉴权, 目前用户只有我一个暂时没有非常强烈的安全性需求</li>
<li><strong>可测试性</strong>: 由于整个系统包括软件前后端以及嵌入式设备, 各部分都需要能够独立开发并独立调试</li>
<li><strong>易用性</strong>: 移动端APP需要最低的学习成本和较美观易用的UI; 嵌入式设备由于目前没有电烙铁焊接, 还是基于模块引脚通过线路直连, 易用性一般</li>
</ul>
<p>可见目前架构中需要实现的质量属性优先级从高到低排序是这样的: 可扩展性, 可用性, 易用性, 可测试性, 安全性, 性能. 在架构设计中需要关注的主要是<strong>可扩展性以及可用性</strong>. 在后面进行架构设计和详细设计时需要考虑. 比如为了实现可扩展性, 采用<strong>统一的通信协议和API</strong>; 为了实现较高的可用性, 服务端需要守护进程及多副本策略; 为了能在保障基本的通信安全, 采用非明文的传输协议等等.</p>
<h4 id="领域模型分析" tabindex="-1">领域模型分析 <a class="header-anchor" href="#领域模型分析" aria-label="Permalink to &quot;领域模型分析&quot;">&ZeroWidthSpace;</a></h4>
<p>**领域驱动设计(DDD)**的概念已经提出很多年了, 能够应对大型系统无法想象的复杂度, <strong>分而治之</strong>. 自己动手做智能家居, 正好是一个从零构建一个软硬件齐全, 涉及面广泛的&quot;复杂&quot;系统, 来亲自实践一下领域驱动设计.<br>
在我对DDD浅显的认识中, DDD首先要做的事情是:</p>
<ul>
<li><strong>抛开软件开发的知识, 专注于业务领域, 把业务需求作为问题域, 分解为多个尽可能关联较少的子域.</strong></li>
<li><strong>识别出子域中的实体、值对象、领域服务、聚合根等构建业务领域的原子</strong></li>
<li><strong>将上面的业务领域模型对应到软件设计领域中, 并以此作为架构设计的参考</strong></li>
</ul>
<p>这些只是<strong>个人感悟</strong>, 并不一定是正确的, 但这样的设计思路是完全符合软件设计的几个原则的: <strong>单一职责(SRP), 最小知识(LKP), 依赖倒置(DIP)</strong>. 每个子域的实现也将会是<strong>高内聚的</strong>, 子域间的耦合也将比&quot;大泥球&quot;式的一起设计实现小很多. 下图是对<strong>智能家居系统子域简单的划分</strong>:
<img src="//filecdn.code2life.top/ddd-iot.jpg" alt="ddd"></p>
<ol>
<li><strong>设备管理子域</strong>是核心子域, 包括对智能家居硬件设备的添加变更以及状态信息展示等管理业务, 通俗的说就是对<strong>设备及相关实体的增删改查</strong></li>
<li><strong>设备监控子域</strong>包括每个<strong>设备是否在线、是否正常</strong>的相关业务, 提供健康监测功能</li>
<li><strong>设备控制子域</strong>包括了每个设备可用的<strong>控制指令</strong>, 以及设备之间的<strong>连锁控制</strong>相关的业务. 这个子域是一个复杂的<strong>状态机</strong>, 每个智能设备接受到某个指令或被动触发某个事件时, 如何响应, 状态如何变化等等. 这部分的设计是系统可扩展性和可修改性的关键.</li>
<li><strong>设备通信子域</strong>是数据交换中心, 对于<strong>每个设备每种指令系统都有统一的原语</strong>去表示, 去传输. 这也是系统可扩展性的关键. 由于不同的智能家居设备差别极大, 设备与服务器, 设备与设备之间如何通信是很有挑战的, 比如使用<strong>适配层</strong>或<strong>代理模式</strong>等等.</li>
</ol>
<p>通过子域的划分, 可以初步识别出业务领域存在这些实体:</p>
<ul>
<li>设备基础信息</li>
<li>设备状态</li>
<li>通信消息</li>
<li>设备控制器</li>
</ul>
<p>注: 由于设备的使用和事件处理应该是看得见并且可随时修改的, <strong>对设备的控制逻辑以及事件处理链也应该是看得见的有标识的实体数据</strong>, 而不是以领域服务的形式存在于系统中, 这样才能使得<strong>可扩展性最大化</strong>.</p>
<h4 id="逻辑架构" tabindex="-1">逻辑架构 <a class="header-anchor" href="#逻辑架构" aria-label="Permalink to &quot;逻辑架构&quot;">&ZeroWidthSpace;</a></h4>
<p>经过领域模型的分析, 服务端可以拆解为<strong>三个子应用服务</strong>和<strong>一个通用服务</strong>, 分别是<strong>设备监控服务, 设备管理服务, 设备控制服务, 外加一个消息中心</strong>. 架构设计应该有多个静态和动态的视图通过不同的UML图来表示, 这里仅画了<strong>模块结构</strong>来体现系统的逻辑架构.
<img src="//filecdn.code2life.top/iot-models.png" alt="model"></p>
<h4 id="部署架构" tabindex="-1">部署架构 <a class="header-anchor" href="#部署架构" aria-label="Permalink to &quot;部署架构&quot;">&ZeroWidthSpace;</a></h4>
<p>通过领域分析和逻辑架构的梳理, 最终的部署架构应该是下图这样的:
<img src="//filecdn.code2life.top/iot-deploy2.png" alt="dep"></p>
<ul>
<li>云端三个应用服务分别部署在不同的Docker容器中(由于我的服务器配置太烂没法上Kubernetes了)</li>
<li>不同的应用服务连接不同的MongoDB数据库(不同的MongoDB属于同一个Docker容器)</li>
<li>虚拟机上直接部署MQTT服务端作为设备通信中心</li>
<li>物联网设备通过家庭Wifi连接到中心消息服务</li>
<li>移动端通过Nginx反向代理与多个应用服务通信</li>
</ul>
<h2 id="技术选型" tabindex="-1">技术选型 <a class="header-anchor" href="#技术选型" aria-label="Permalink to &quot;技术选型&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="通信协议" tabindex="-1">通信协议 <a class="header-anchor" href="#通信协议" aria-label="Permalink to &quot;通信协议&quot;">&ZeroWidthSpace;</a></h4>
<p>整个系统的通信可以分为应用软件的通信与底层嵌入式设备的通信, 应用层无疑是采用<strong>REST风格的HTTP协议</strong>作为统一的通信方式, 但嵌入式设备的通信协议五花八门, 比如<a href="//www.eefocus.com/embedded/380968" target="_blank" rel="noreferrer">这里</a>就列举了11种通信方式, 那如何选择合适的物联网通信协议呢? 我们分别对比一下常见通信协议的优劣势.</p>
<ul>
<li><strong>蓝牙</strong>:  2.4GHz频率/<strong>1Mbps速率</strong>/50~150米通信距离/<strong>低功耗</strong></li>
<li><strong>ZigBee</strong>: 2.4GHz频率/<strong>0.25Mbps速率</strong>/10~100米通信距离/<strong>低功耗</strong></li>
<li><strong>Wifi</strong>: 2.4GHz 5GHz频率/<strong>150~600Mbps</strong>/50米通信距离/<strong>高功耗</strong></li>
<li><strong>蜂窝网络</strong>: 900-1800-1900-2100MHz频率/35Kbps~10Mbps速率/<strong>有基站就有通信</strong>/中等偏高的功耗</li>
</ul>
<p>常见的底层通信协议中, 米家的门窗传感器选择的是<strong>ZigBee协议</strong>, 我现在选择的是<strong>Wifi</strong>, 一方面因为功耗通过大容量电池可以解决, 另一方面<strong>通过Wifi可以复用基于TCP之上的无数应用层协议, 实现与高层应用服务直接打通</strong>, 减少通信数据适配层的开发量. 经过初步测算, ESP8266设置成station模式处于低功耗工作状态下电流约0.3mA, 两节5号电池大约3000mAh的容量, 大约可以使用1年. 但米家的产品采用ZigBee的低功耗通信, 一个240mAh的纽扣电池就可以用两年以上, 商用肯定Zigbee和蓝牙是正确的选择.</p>
<p>在Wifi之上, 数据传输格式相关协议也有很多选择, 比如嵌入式设备直接处理HTTP/HTTPs协议的数据, 32位mcu的ESP8266芯片处理是没有压力的, 但考虑到物联网世界与互联网的差别, 选择了更适合物联网应用场景的<strong>MQTT协议</strong>, <a href="https://mcxiaoke.gitbooks.io/mqtt-cn/content/" target="_blank" rel="noreferrer">这里有MQTT的详细介绍</a>, 它同样构建于TCP/IP之上, 但比HTTP小巧, 而且设计初衷就是一个Broker架构的消息发布-订阅框架, 非常<strong>适合物联网设备在网络不可靠情况下的全双工通信</strong>.</p>
<p>总结一下, 通信协议的技术选型是这样的:</p>
<ul>
<li>移动端APP-服务器通信: <strong>HTTP协议, RESTful WebAPI, 另外使用WebSocket做全双工通信</strong></li>
<li>设备-服务器通信: <strong>MQTT协议, 自定义数据格式</strong></li>
<li>设备-设备通信(目前还没有): <strong>ZigBee协议- Wifi数据适配层 - MQTT协议</strong></li>
</ul>
<h4 id="嵌入式设备" tabindex="-1">嵌入式设备 <a class="header-anchor" href="#嵌入式设备" aria-label="Permalink to &quot;嵌入式设备&quot;">&ZeroWidthSpace;</a></h4>
<p>上面的部署视图已经暴露了硬件设备和芯片的选型:</p>
<ul>
<li>Wifi: <strong>ESP8266-01S</strong></li>
<li>蜂鸣器: <strong>无源蜂鸣器一枚</strong></li>
<li>电源: <strong>2节AA电池 / 5V充电宝+电源模块</strong></li>
<li>传感器: <strong>干簧管传感器一枚</strong></li>
</ul>
<p>其实一开始想做门窗传感器的时候, 想到的是<strong>超声波测距模块</strong>, 但看到米家的产品后发现原来还有<strong>干簧管</strong>这么神奇的东西, 外加一块永磁体就能判断距离了, 而且几乎是<strong>零功耗</strong>的.</p>
<h4 id="服务端" tabindex="-1">服务端 <a class="header-anchor" href="#服务端" aria-label="Permalink to &quot;服务端&quot;">&ZeroWidthSpace;</a></h4>
<p>应用服务部署在个人的<strong>阿里云ECS</strong>上, 为了更快的开发和更小的资源占用(买的最低端的ECS服务器只有<strong>1GB内存1核CPU</strong>), 以及更方便的WebSocket编程, 暂定使用<strong>Node.js</strong>全家桶. 服务端技术选型有:</p>
<ul>
<li>基础设施: <strong>Docker容器</strong></li>
<li>数据库: <strong>MongoDB</strong></li>
<li>应用框架: <strong>Koa2</strong></li>
<li>实时通信: <strong>socket.io</strong></li>
</ul>
<h4 id="移动端app" tabindex="-1">移动端APP <a class="header-anchor" href="#移动端app" aria-label="Permalink to &quot;移动端APP&quot;">&ZeroWidthSpace;</a></h4>
<p>移动端开发并不是我擅长的领域, 目前业务逻辑不太复杂, 又有快速开发和跨平台的需求, 准备尝试一波Hybrid + Vue.js来做移动端App. Hybrid开发暂定用<strong>DCloud平台</strong>, 但不准备使用DCloud家的<a href="//dev.dcloud.net.cn/mui/" target="_blank" rel="noreferrer">MUI</a>, 因为如今的前端开发再不用<strong>MVVM</strong>就太不Geek了. 相比之下, Vue在移动端也有很多优秀的组件库, 比如:</p>
<ul>
<li><a href="https://github.com/ElemeFE/mint-ui" target="_blank" rel="noreferrer">Mint UI</a> 10.1k star, Element UI的移动端版本, 组件不多</li>
<li><a href="https://github.com/airyland/vux" target="_blank" rel="noreferrer">vux</a> 12.1k star, 组件很多但无专职团队维护</li>
<li><a href="https://github.com/wangdahoo/vonic" target="_blank" rel="noreferrer">vonic</a> 2.6k star / <a href="https://www.youzanyun.com/zanui/vant#/zh-CN/sku" target="_blank" rel="noreferrer">vant</a> 2k star / Keen-UI 3.3k star</li>
</ul>
<p>前端组件库中选择了<strong>MintUI</strong>, 整个移动端技术选型总结一下大概有这些:</p>
<ul>
<li>Hybrid框架: <strong>Dcloud</strong></li>
<li>前端MVVM框架: <strong>Vue.js + Vuex</strong></li>
<li>前端组件库和第三方库: <strong>MintUI, axios, socket.io, ...</strong></li>
<li>前端打包: <strong>Webpack</strong></li>
</ul>
<p>架构设计和技术选型搞定了, 下面就开始详细设计和撸代码了。</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Node.js进阶] 使用Node.js V8 API编写C++扩展]]></title>
            <link>https://code2life.top/blog/0024-node-v8-extension</link>
            <guid>https://code2life.top/blog/0024-node-v8-extension</guid>
            <pubDate>Tue, 17 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="node-js进阶-使用node-js-v8-api编写c-扩展" tabindex="-1">[Node.js进阶] 使用Node.js V8 API编写C++扩展 <a class="header-anchor" href="#node-js进阶-使用node-js-v8-api编写c-扩展" aria-label="Permalink to &quot;[Node.js进阶] 使用Node.js V8 API编写C++扩展&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>2020年更新：这篇是传统的NodeJS Addon开发方式，现在Node.js还提供了N-API的能力，屏蔽了V8这一层，对Addon开发更加友好了，新项目建议直接上N-API。</p>
</blockquote>
<h2 id="引言" tabindex="-1">引言 <a class="header-anchor" href="#引言" aria-label="Permalink to &quot;引言&quot;">&ZeroWidthSpace;</a></h2>
<p>Node.js本身已经提供了非常多跨平台的能力, 但对于一些特殊的场景仍不能满足需求. 比如:</p>
<ul>
<li>需要调用特定平台上的API</li>
<li>集成某些已有的C/C++编写的动态/静态库</li>
<li>有高性能需求或需要使用多线程特性的功能等.</li>
</ul>
<p>对于这些场景, Node.js也提供了基于V8引擎的扩展能力. 但这种能力的扩展过于依赖V8引擎, 现在Node.js已经开始试验性的提供**<a href="https://nodejs.org/dist/latest-v8.x/docs/api/n-api.html" target="_blank" rel="noreferrer">N-API</a><strong>的方式来进行C++扩展, 以此来</strong>屏蔽不同版本的Node.js、不同Js引擎的差异**. 关于原生C++扩展的开发方式的变化, <a href="//weixin.niurenqushi.com/article/2017-06-26/4925408.html" target="_blank" rel="noreferrer">这里</a>有一篇很不错的文章.</p>
<p>本文主要介绍更常用和稳定的<a href="https://nodejs.org/dist/latest-v8.x/docs/api/addons.html" target="_blank" rel="noreferrer">V8引擎C++扩展</a>, 内容很多摘自官方文档. 利用C++扩展在Github已经有一些有意思的项目, 比如播放声音<a href="https://github.com/TooTallNate/node-speaker" target="_blank" rel="noreferrer">node-speaker</a>, 封装Qt组件库<a href="https://github.com/arturadib/node-qt" target="_blank" rel="noreferrer">node-qt</a>等等. 本文以获取windows下的盘符和容量为例来说明.</p>
<h2 id="基础概念" tabindex="-1">基础概念 <a class="header-anchor" href="#基础概念" aria-label="Permalink to &quot;基础概念&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li><strong>V8:</strong> V8是Node.js默认的JavaScript引擎, 通过<strong>JIT编译</strong>实现高性能的Js解析执行. Node.js源代码中的deps/v8/include/v8.h, 以及<a href="https://v8docs.nodesource.com/" target="_blank" rel="noreferrer">Node使用的V8引擎文档</a>都可以查阅.</li>
<li><strong>libuv:</strong> 一个<strong>跨平台</strong>的异步线程调用库, 实现了 Node.js 的事件循环、工作线程、以及平台所有的的异步操作的C库. 提供了一个类似 POSIX 多线程的线程抽象，可被用于强化更复杂的需要超越标准事件循环的异步插件. 文档传送门: <a href="//docs.libuv.org/en/v1.x/api.html" target="_blank" rel="noreferrer">这里</a>, <a href="//filecdn.code2life.top/uv-book-cn.pdf" target="_blank" rel="noreferrer">这里还有一个中文教程</a></li>
<li><strong>node-gyp:</strong> Node.js C++模块的编译工具, <strong>npm install -g node-gyp</strong> 后可使用命令行进行Node C++扩展的管理和编译等操作. node-gyp是nodejs的一个子项目, 项目地址在<a href="https://github.com/nodejs/node-gyp" target="_blank" rel="noreferrer">这里</a>, 其中也有详细的文档链接.
<ul>
<li>node-gyp configure: 生成一个Node C++项目, 需要事先在目录中放入一个binding.gyp文件</li>
<li>node-gyp build/rebuild/clean: 编译/重新编译/清理项目, 在windows中调用Visual Studio, linux中调用gcc等工具实现编译</li>
</ul>
</li>
<li><strong>nan</strong>: 全称是 Native Abstractions for Node.js, 是一个用于C++扩展开发的npm模块. V8引擎在不断更新迭代, Node.js本身也在更新迭代, 按照当前V8提供的API编写的模块也许过一段时间就无法编译运行了. 于是nan出现了, 它可以屏蔽各个Node以及V8版本的差异, 提供统一的API和宏来进行C++扩展开发. 在<strong>实际应用中应该尽量使用nan进行C++扩展开发, 不要使用底层的V8/libuv API</strong></li>
</ul>
<h4 id="如何编写binding-gyp" tabindex="-1">如何编写Binding.gyp <a class="header-anchor" href="#如何编写binding-gyp" aria-label="Permalink to &quot;如何编写Binding.gyp&quot;">&ZeroWidthSpace;</a></h4>
<p>binding.gyp详细的用法见<a href="https://gyp.gsrc.io/docs/UserDocumentation.md" target="_blank" rel="noreferrer">文档</a>, 这里是一个最简单的用法:</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>{ </span></span>
<span class="line"><span>  "targets": [ </span></span>
<span class="line"><span>    { </span></span>
<span class="line"><span>      "target_name": "hello", </span></span>
<span class="line"><span>      "sources": [ "src/hello.cc" ]</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  ]</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h4 id="如何使用v8引擎" tabindex="-1">如何使用V8引擎 <a class="header-anchor" href="#如何使用v8引擎" aria-label="Permalink to &quot;如何使用V8引擎&quot;">&ZeroWidthSpace;</a></h4>
<p>另外, 这是一个使用V8 API创建JS执行上下文并编译运行HelloWorld的例子.</p>
<div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> &#x3C;v8.h></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> argc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">char*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> argv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[]) {  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 声明HandleScope用于存放Handle, 执行完毕后释放掉其中的Handle</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 声明变量之后, 所有的Local Handle都会在此HandleScope下管理</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // main函数运行完毕栈推出, handle_scope生命周期结束, Handle被释放</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  HandleScope handle_scope;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // JavaScript执行上下文, Persistent声明的对象</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 不受Handle/HandleScope管理, 需要单独调用Dispose</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Persistent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Context</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 可以认为是JavaScript的作用域</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  Context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Scope </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">context_scope</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context);  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Handle是V8引擎对Heap中对象的引用</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // V8编程中必须使用Handle去引用一个堆中的对象, 否则无法被V8管理和GC</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Handle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">String</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> source </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"'Hello' + ', World!'"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 编译JS代码</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Handle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Script</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> script </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Compile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(source);  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 在当前context_scope下运行JS脚本</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Handle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> script-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //释放JavaScript执行上下文</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  context.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Dispose</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 打印V8中的运行结果</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::AsciiValue </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ascii</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(result);  </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">%s\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ascii);  </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><h2 id="代码实现" tabindex="-1">代码实现 <a class="header-anchor" href="#代码实现" aria-label="Permalink to &quot;代码实现&quot;">&ZeroWidthSpace;</a></h2>
<p>编写C++扩展, 首先需要在目录中建立一个<strong>binding.gyp</strong>文件, 再执行<strong>node-gyp configure</strong>创建对应平台下的项目, C++文件中使用<strong>NODE_SET_METHOD, NODE_MODULE</strong>等导出CommonJs模块.</p>
<h4 id="从helloworld开始" tabindex="-1">从HelloWorld开始 <a class="header-anchor" href="#从helloworld开始" aria-label="Permalink to &quot;从HelloWorld开始&quot;">&ZeroWidthSpace;</a></h4>
<p>这是一个官网文档中的示例:</p>
<div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> &#x3C;node.h></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> demo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::FunctionCallbackInfo;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Isolate;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Local;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Object;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::String;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Value;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SayHello</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> FunctionCallbackInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        Isolate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isolate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetIsolate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetReturnValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Hello World"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Local</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        NODE_SET_METHOD</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(exports, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"hello"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, SayHello);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    NODE_MODULE</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(NODE_GYP_MODULE_NAME, init)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><h4 id="数据类型转换" tabindex="-1">数据类型转换 <a class="header-anchor" href="#数据类型转换" aria-label="Permalink to &quot;数据类型转换&quot;">&ZeroWidthSpace;</a></h4>
<p>V8编程中大部分变量都是通过Local模板管理的, 这些变量由V8的GC控制, V8的数据类型很多, 基本与JavaScript的数据类型都有对应, 这里是一张V8引擎数据类型的汇总图
<img src="//filecdn.code2life.top/v8-data-type.png" alt="v8">
在编写C++扩展时, C++标准库的数据类型转换成V8数据类型的方法如下:</p>
<div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//namespace v8</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Isolate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isolate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetIsolate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Number 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Number</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> retval </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// String 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">String</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Hello World!"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Object 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Object</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> obj </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 对象的赋值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg1"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), str);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg2"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), retval);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Function 类型的声明并赋值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">FunctionTemplate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tpl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">FunctionTemplate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, MyFunction);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Function</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> fn </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tpl-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetFunction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 函数名字</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">fn-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SetName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"theFunction"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">v8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg3"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), fn);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Boolean 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Boolean</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> flag </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Boolean</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg4"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), flag);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Array 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Array 赋值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">arr-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">arr-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">arr-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg5"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), arr);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Undefined 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> und </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg6"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), und);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// null 类型的声明</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> null </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">obj-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"arg7"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), null);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 返回给 JavaScript 调用时的返回值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetReturnValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br></div></div><h4 id="实现获取盘符和磁盘容量" tabindex="-1">实现获取盘符和磁盘容量 <a class="header-anchor" href="#实现获取盘符和磁盘容量" aria-label="Permalink to &quot;实现获取盘符和磁盘容量&quot;">&ZeroWidthSpace;</a></h4>
<p>实现获取盘符以及容量, 涉及到一些Windows API, 具体代码如下, 参考了**<a href="https://github.com/jduncanator/node-diskusage" target="_blank" rel="noreferrer">diskusage</a>模块**的部分代码, 项目的代码已经上传到我的Github(<a href="https://github.com/Code2Life/node-disk" target="_blank" rel="noreferrer">node-disk</a>)中. 这里贴一些关键的代码片段, 包括libuv的异步调用以及windows API调用等.</p>
<p>首先需要定义模块的入口函数, 处理输入参数, 并将调用逻辑封装到libuv中. 关键代码如下:</p>
<div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> GetDiskInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> FunctionCallbackInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Isolate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isolate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetIsolate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //自定义的结构体, 用于在libuv的异步调用中传递输入输出</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  async_req</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> req </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> async_req;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  req->req.data </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> req;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //判断参数的正确性</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">IsString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">IsFunction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    node</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ErrnoException</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Parameter error"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">NULL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::Utf8Value </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">param</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ToString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  req->input </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> std</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">param);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  req->isolate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isolate;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Function</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> callback </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Local</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Cast</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(args[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  req->callback.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Reset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, callback);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //放到libuv队列中等待被调用</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  uv_queue_work</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">uv_default_loop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    &#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">req->req,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    DoAsync,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    (uv_after_work_cb)AfterAsync);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //返回给js调用端的值</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  args.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetReturnValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Boolean</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//NODE_SET_METHOD相当于js文件中的exports.xxx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> init</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Local</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  NODE_SET_METHOD</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(exports, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"getDiskInfo"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, GetDiskInfo);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//声明module</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NODE_MODULE</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(NODE_GYP_MODULE_NAME, init)</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><p>上面的代码中<strong>DoAsync</strong>是关键的实现入口, 其中可能包括一些复杂的计算或IO操作, 但由于在异步线程中进行, 不会影响node.js的事件循环. 此处要注意的是: <strong>切忌在DoAsync中封装V8引擎的数据, 因为DoAsync中的变量会随着调用栈的推出销毁局部变量, 无法利用回调带回给JS</strong>.</p>
<p>DoAsync执行完毕后libuv会触发回调, 也就是代码中的<strong>AfterAsync</strong>函数, 在这里需要<strong>将回调给V8引擎的数据</strong>封装好, 并<strong>销毁</strong>掉不再使用的堆中的变量防止内存泄露. AfterAsync函数的关键代码如下:</p>
<div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//封装回调给V8引擎的函数实参</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">result-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req->isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"total"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req->isolate, info->totalSize.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">c_str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">result-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req->isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"free"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req->isolate, info->freeSize.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">c_str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">result-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">NewFromUtf8</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(req->isolate, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"volumes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), volumes);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//JS回调函数 function(arg1, arg2){} 即可取到这里声明的两个Local变量</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> argv[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate), result };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">TryCatch</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> try_catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">isolate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Object</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> global </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> isolate-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetCurrentContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Global</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Local</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Function</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> callback </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Local</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">New</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, req->callback);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//V8引擎中执行回调函数, 然后清理callback和req的堆内存</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">callback-></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(global, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, argv);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">req->callback.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Reset</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果还有其他new出来的对象也要再此及时清理防止内存泄露</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">delete</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> req;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果在libuv线程池的执行过程出错, 反馈给node进程</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果js代码不去捕捉错误, 可以全局的使用process.on('uncaughtException')捕捉, 否则进程会退出</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (try_catch.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">HasCaught</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  node</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">::</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">FatalException</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(isolate, try_catch);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>V8引擎和libuv的调用核心代码大概就是这些, 具体的获取磁盘容量的实现不再赘述, 传送至<a href="https://github.com/Code2Life/node-disk" target="_blank" rel="noreferrer">Github</a>. 调用Windows API中的<strong>GetLogicalDrives</strong>, <strong>GetDiskFreeSpaceEx</strong>等函数代码在<a href="https://github.com/Code2Life/node-disk/blob/master/src/node_disk_win.cc" target="_blank" rel="noreferrer">node_disk_win.cc</a>文件中.</p>
<p>另外, 这些代码已经发布到了npm上, 可以通过<strong>npm install node-disk</strong>下载使用, 目前只支持windows平台, 后续打算支持linux和macOS, 以及实现获取更详尽的磁盘信息的API. 如果有人对此有兴趣, <strong>非常欢迎加入node-disk模块的迭代和维护.</strong></p>
<p>贴个图纪念一下1.0.0版本:
<img src="//filecdn.code2life.top/node-disk.jpg" alt="node-disk"></p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>本文简单讲解了V8, libuv的一些基础, 并以windows下获取磁盘信息为例阐述了Node.js C++扩展的编写方式. 在实际应用中, 使用<strong>nan模块提供的抽象接口</strong>开发C++扩展具有更好的兼容性, 此处更多的是学习<strong>V8及libuv底层的调用</strong>, 因此未使用nan的方式开发. 案例比较简单涉及到C++的部分不深, 以后有机会再去学习一些更深入的C++编程吧.</p>
<p>不同的语言之间没有好坏之分, 适合需求的才是最好的. 很多时候, 为了达到特定的质量属性或实现某些复杂的功能, 需要的是多语言的配合. Node.js能有这么多的应用场景, 其中有一些是离不开C/C++, 甚至是python的. <strong>根据需求扬长避短来进行技术选型才是正道.</strong></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[分布式专题] 一次Etcd集群宕机引发的思考]]></title>
            <link>https://code2life.top/blog/0023-etcd-thinking</link>
            <guid>https://code2life.top/blog/0023-etcd-thinking</guid>
            <pubDate>Thu, 12 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="分布式专题-一次etcd集群宕机引发的思考" tabindex="-1">[分布式专题] 一次Etcd集群宕机引发的思考 <a class="header-anchor" href="#分布式专题-一次etcd集群宕机引发的思考" aria-label="Permalink to &quot;[分布式专题] 一次Etcd集群宕机引发的思考&quot;">&ZeroWidthSpace;</a></h1>
<p>前一段时间学习Kubernetes的过程中接触到Etcd, 它是一个<strong>强一致性</strong>的分布式<strong>键值存储数据库</strong>, 亦可作为<strong>服务发现的存储仓库</strong>, Kubernetes将所有集群的信息存储在Etcd中. 在应用场景和功能特性上与<strong>ZooKeeper</strong>非常类似, 但也有一些区别。笔者前不久在虚拟机中折腾了一个单节点的Kubernetes&quot;集群&quot;, 但几次不小心一脚踹掉了电源线导致了整个&quot;集群&quot;断电. <strong>第三次踹掉电源线时, 意外发生了, kubectl命令全部因为请求kube-apiserver超时失败.</strong></p>
<h2 id="定位原因" tabindex="-1">定位原因 <a class="header-anchor" href="#定位原因" aria-label="Permalink to &quot;定位原因&quot;">&ZeroWidthSpace;</a></h2>
<p>一开始发现这个问题是虚拟机启动后, 打开Kubernetes的Dashboard无法显示, SSH进去执行kubectl报错:
<img src="//filecdn.code2life.top/k8s-timeout-error.png" alt="err">
然后开始检查各项服务是否正常</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#报错: timeout</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> componentstatus</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">kubectl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> cluster-info</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kube-apiserver.service</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kubelet.service</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> etcd.service</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>执行到查询etcd服务状态时, 发现服务启动失败了, 一直在自动重启中的状态, 用journalctl命令查询日志</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>journalctl -xe</span></span>
<span class="line"><span>journalctl -u etcd</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p><img src="//filecdn.code2life.top/etcd-fail.png" alt="err">
问题很明确了, 是因为断电导致Etcd的日志wal文件损坏, 而且Etcd集群节点数量只有1, <strong>损坏数量超过了 (N - 1) / 2</strong>, 所以Etcd集群无法恢复数据, 导致启动失败.</p>
<h2 id="刨根问底-cap定理与一致性算法" tabindex="-1">刨根问底: CAP定理与一致性算法 <a class="header-anchor" href="#刨根问底-cap定理与一致性算法" aria-label="Permalink to &quot;刨根问底: CAP定理与一致性算法&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>CAP定理：一个分布式系统<strong>不可能</strong>同时满足**一致性(C), 可用性(A)和分区容错性(P)**这三个基本需求, 最多只能同时满足其中的两项.</p>
</blockquote>
<p>Etcd与Mongodb这样的分布式存储数据库一样, 选择了<strong>一致性(C)和分区容错性(P)</strong>, 一定程度上牺牲了可用性. 笔者从<a href="https://www.cnblogs.com/xybaby/p/6871764.html" target="_blank" rel="noreferrer">这里</a>摘录了一张主流数据库选择了CAP中的哪两个的图片:
<img src="//filecdn.code2life.top/cap.png" alt="cap"></p>
<h4 id="从一致性算法说起" tabindex="-1">从一致性算法说起 <a class="header-anchor" href="#从一致性算法说起" aria-label="Permalink to &quot;从一致性算法说起&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>一致性算法需要解决的问题是: 如何在一个可能发生<strong>机器宕机或网络异常</strong>等异常情况的分布式系统中, 快速且正确地在集群内部对某个数据的值达成一致, 并且保证不论发生以上任何异常, 都不会破坏整个系统的<strong>一致性</strong>.</p>
</blockquote>
<p>常见的一致性算法有<strong>Paxos、Raft、PBFT</strong>等. ZooKeeper底层使用<strong>基于Paxos扩展出来的Zab协议</strong>实现一致性, 而本文的主角Etcd底层使用的是<strong>Raft协议</strong>. PBFT(拜占庭容错算法)也是一种共识算法, 主要在可能存在不可信节点的集群中保障一致性, 应用场景在区块链中, 这里不做展开.</p>
<ul>
<li><a href="https://www.cnblogs.com/linbingdong/p/6253479.html" target="_blank" rel="noreferrer">Paxos算法原理与推导</a></li>
<li><a href="https://www.jianshu.com/p/fb5edf031afd" target="_blank" rel="noreferrer">PBFT算法原理</a></li>
<li><a href="//thesecretlivesofdata.com/raft" target="_blank" rel="noreferrer">Raft算法动画演示</a></li>
</ul>
<h4 id="raft算法简述" tabindex="-1">Raft算法简述 <a class="header-anchor" href="#raft算法简述" aria-label="Permalink to &quot;Raft算法简述&quot;">&ZeroWidthSpace;</a></h4>
<p>Raft算法的确比Paxos算法要容易理解的多, 算法的核心是一个状态转换机, 任何一个节点的状态为这三种状态之一:</p>
<ul>
<li><strong>Leader:</strong> 处理所有客户端交互, 日志复制等, 一般一次只有一个Leader, 在出现网络分区时会选举出多个Leader, Leader发现存在别的Leader比自己的任期长就回归Follower</li>
<li><strong>Follower:</strong> 接受Leader的消息被动改变自己的状态, 如追加日志, 接受commit等</li>
<li><strong>Candidate:</strong> 当Follower在150~300ms内未接受到Leader的Heartbeat时, 会开始Leader选举, 转换为候选节点拉票, 当Candidate拉到半数以上节点的票票后就会成为Leader<br>
<img src="//filecdn.code2life.top/raft-state.png" alt="state"></li>
</ul>
<p>在节点的状态转换过程中涉及到这几个概念: <strong>任期(Term)、Leader选举、日志复制</strong>等, 算法的细节较多, 完整的算法详细可参考这篇<a href="https://raft.github.io/raft.pdf" target="_blank" rel="noreferrer">斯坦福大学的论文</a>. Raft算法中关键的几个点如下, 部分摘录自<a href="//www.cnblogs.com/cchust/p/5634782.html" target="_blank" rel="noreferrer">这篇Blog</a>.</p>
<p><strong>如何进行leader选举</strong></p>
<ul>
<li>每个follower节点都有成为leader的想法, 在初始化阶段或leader节点宕机时, 一轮任期的选举开始.</li>
<li>follower节点在一个随机的时间片结束后会进入候选状态, 告诉其他节点投自己的票.</li>
<li>每个任期中每个follower只能投一票, 并且会投给第一个接受到的投票请求对应的候选节点.</li>
<li>当一个候选节点的票数过半时就成为leader, 新一轮任期正式开始, 同时leader向其他节点发送心跳包(包含日志复制的消息)来确认数据的一致性.</li>
</ul>
<p><strong>如何进行日志复制</strong></p>
<ul>
<li>客户端发送一个改变数据的请求X,</li>
<li>Leader将X写入自己的日志中, 并向所有Follower发送日志追加(Append Entries)请求A(X)</li>
<li>Follower接受A(X), 将X的数据变更写入日志并返回给Leader</li>
<li>当不少于(N - 1) / 2 个Follower(加上自己已经过半)响应成功时, Leader发起commit该数据并向所有节点发送commit请求C(X), 同时响应客户端</li>
</ul>
<p><strong>如何确保宕机节点恢复后数据的一致性:</strong></p>
<ul>
<li>leader向follower发送日志时, 会顺带邻近的前一条日志, follwer接收日志时, 会在相同任期号和索引位置找前一条日志, 如果存在且匹配, 则接收日志; 否则拒绝, leader会<strong>减少日志索引位置并进行重试</strong>, 直到某个位置与follower达成一致. 然后follower<strong>删除索引后的所有日志</strong>, 并追加leader发送的日志, 在follower落后太多超过某个阈值时leader会选择直接<strong>发送快照</strong>让follower尽快与最新的数据保持一致, 一旦日志追加成功, 则follower和leader的所有日志就保持一致. 只有在多数派的follower都响应接受到日志后, 表示事务可以提交, 才能返回客户端提交成功. 这是保障数据一致性的关键点, 确保了Follower节点一定与Leader节点的数据完全一致. 这与ZAB协议不同, Raft算法是不允许节点出现数据&quot;空洞&quot;的.</li>
</ul>
<p><strong>如何在网络异常导致出现多leader的情况下恢复:</strong></p>
<ul>
<li>网络分区可能会导致出现多个leader, 当分区被消除时需要恢复成单个leader. 这时如果任期小的leader达成了多数派, 则说明任期大的节点以前是leader, 拥有最多的日志, 但是<strong>没有达成多数派, 因此它的日志可以被覆盖</strong>. 但该节点会尝试继续投票, 新leader发送日志给该节点, 如果leader发现返回的termT&gt;currentTerm, 且还没有达成多数派, 则重新变为follower, 促使TermId更大的节点成为leader. 但并不保证拥有较大TermId的节点一定会成为leader, 因为leader是优先判断是否达成多数派, 如果<strong>已经达成多数派了, 则继续为leader, 即使是任期较小的leader</strong>.</li>
</ul>
<h2 id="按图索骥-etcd的原理和架构" tabindex="-1">按图索骥: Etcd的原理和架构 <a class="header-anchor" href="#按图索骥-etcd的原理和架构" aria-label="Permalink to &quot;按图索骥: Etcd的原理和架构&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="etcd的组件结构" tabindex="-1">Etcd的组件结构 <a class="header-anchor" href="#etcd的组件结构" aria-label="Permalink to &quot;Etcd的组件结构&quot;">&ZeroWidthSpace;</a></h4>
<p>Etcd主要分为四个部分:</p>
<ul>
<li><strong>HTTP Server：</strong> 用于处理用户发送的API请求以及其它etcd节点的同步与心跳信息请求.</li>
<li>**Store：**用于处理etcd支持的各类功能的事务, 包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等, 是etcd对用户提供的大多数API功能的具体实现.</li>
<li>**Raft：**Raft强一致性算法的具体实现, 是Etcd的核心.</li>
<li>**WAL：**Write Ahead Log（预写式日志）, 是etcd的数据存储方式. 除了在内存中存有所有数据的状态以及节点的索引以外, etcd就通过WAL进行持久化存储. WAL中, 所有的数据提交前都会事先记录日志. Snapshot是为了防止数据过多而进行的状态快照；Entry表示存储的具体日志内容.
<img src="//filecdn.code2life.top/etcd-arch.jpg" alt="arch"></li>
</ul>
<p>通常, 一个用户的请求发送过来, 会经由<strong>HTTP Server转发给Store</strong>进行具体的事务处理(如果是Proxy模式的Etcd节点接受请求会反向代理给Etcd集群的其他节点), 如果涉及到节点的<strong>修改</strong>, 则交给<strong>Raft模块</strong>进行状态的变更、日志的记录, 然后再同步给别的etcd节点以确认数据提交, 最后进行数据的提交, 再次同步. 官网有详细的<a href="https://coreos.com/etcd/docs/latest/" target="_blank" rel="noreferrer">参考文档</a></p>
<h4 id="实现集群化" tabindex="-1">实现集群化 <a class="header-anchor" href="#实现集群化" aria-label="Permalink to &quot;实现集群化&quot;">&ZeroWidthSpace;</a></h4>
<p>由于Raft算法在做决策时需要多数节点的投票, 所以Etcd一般部署集群推荐<strong>奇数个节点</strong>, 推荐的数量为3、5或者7个节点构成一个集群, 官方推荐数量为5个节点.Etcd集群有三种配置方案, 在<a href="https://blog.csdn.net/u010511236/article/details/52386229" target="_blank" rel="noreferrer">这篇文章</a>中有详细的描述:</p>
<ul>
<li><strong>静态配置启动:</strong> 通过<strong>initial-cluster</strong>参数配置预先指定的Etcd节点实例</li>
<li><strong>Etcd自身服务发现:</strong> 通过<strong>discovery</strong>参数指定用已有的Etcd服务来作为服务注册中心发现服务, 官方提供了一个公用的接口:<a href="https://discovery.etcd.io/new?size=3" target="_blank" rel="noreferrer">https://discovery.etcd.io/new?size=N</a>,</li>
<li><strong>通过DNS进行服务发现:</strong> 通过<strong>discovery-srv</strong>参数以及约定的DNS配置实现</li>
</ul>
<p>这是笔者虚拟机中Kubernetes连接的Etcd集群配置, 使用的是静态配置方式, 只配置了一个名为etcd1的节点</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/root/local/bin/etcd</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --name=etcd1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --cert-file=/etc/etcd/ssl/etcd.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --key-file=/etc/etcd/ssl/etcd-key.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --peer-cert-file=/etc/etcd/ssl/etcd.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --peer-key-file=/etc/etcd/ssl/etcd-key.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --trusted-ca-file=/etc/kubernetes/ssl/ca.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --initial-advertise-peer-urls=https://192.168.113.131:2380</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --listen-peer-urls=https://192.168.113.131:2380</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --listen-client-urls=https://192.168.113.131:2379,//127.0.0.1:2379</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --advertise-client-urls=https://192.168.113.131:2379</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --initial-cluster-token=etcd-cluster-0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --initial-cluster=etcd1=https://192.168.113.131:2380</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --initial-cluster-state=new</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    --data-dir=/var/lib/etcd</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><h2 id="解决问题" tabindex="-1">解决问题 <a class="header-anchor" href="#解决问题" aria-label="Permalink to &quot;解决问题&quot;">&ZeroWidthSpace;</a></h2>
<p>Etcd集群中出现**(N - 1) / 2** 个节点异常或损坏对集群是<strong>没有影响</strong>的, 但在<strong>单节点</strong>的情况下一次断电就可能导致数据无法恢复了. 所以最可靠的办法就是<strong>定期的备份</strong>, 出现灾难性的事件时也能恢复, 具体做法在<a href="https://coreos.com/etcd/docs/latest/op-guide/recovery.html" target="_blank" rel="noreferrer">这里</a>, 通过<strong>etcdctl snapshot save/restore</strong>来备份/恢复快照.</p>
<p>然而这次的问题比较麻烦, 不仅是单节点的Etcd, 而且没有备份过数据. 经过了以下尝试:</p>
<ul>
<li>删除wal目录中的.broken文件: 再次启动仍然报错(read wal error (walpb: crc mismatch) and cannot be repaired)</li>
<li>查看损坏的.wal文件, 发现结尾处写入了很多无意义的字符串和空白. 直接删除文件, 会报错找不到wal文件</li>
<li>删除整个wal目录, etcd启动无问题, 但kubernetes内部相关的容器中除了flannel网络, 其他全挂</li>
<li>继续通过kubectl命令查询(kubectl describe po kube-dns-566c7c77d8-n8hkf -n kube-system), 发现Pod启动错误</li>
<li>继续深入kubectl logs和docker logs查询容器粒度的日志信息, 并未找到有用的错误信息</li>
</ul>
<p>一番折腾之后感觉已经无力回天, 还有最后两个办法: <strong>通过yml重新设置Kubernetes的Pods, Service, Ingress等; 或者恢复虚拟机快照.</strong> 用了第一个办法简单粗暴的解决了问题, 所有的Pod/Service都正常运行了, 但总感觉还是没有解决源头的问题, 日后还需多加学习啊.<br>
这是重新部署恢复后的Pod面板监控截图:
<img src="//filecdn.code2life.top/k8s-dashboard.png" alt="k8s"></p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>通过一次无意间的断电引发的问题, 可以引申出很多背后原理性的知识. 依葫芦画瓢学会怎么用一项技术没有意义, 怎么在出现问题时通过对<strong>原理和本质</strong>的思考快速定位解决问题才算是到达了掌握的水平.
<img src="//filecdn.code2life.top/dake.jpg" alt="dake">
最近深感跌入绝望之谷, 送自己一句话吧: <strong>万丈高楼平地起, 勿在浮沙筑高台.</strong></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[C# 异步更新UI线程的方法]]></title>
            <link>https://code2life.top/blog/0022-csharp-async-update</link>
            <guid>https://code2life.top/blog/0022-csharp-async-update</guid>
            <pubDate>Thu, 05 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="c-异步更新ui线程的方法" tabindex="-1">C# 异步更新UI线程的方法 <a class="header-anchor" href="#c-异步更新ui线程的方法" aria-label="Permalink to &quot;C# 异步更新UI线程的方法&quot;">&ZeroWidthSpace;</a></h1>
<p>在做Winform或WPF客户端开发时, 最基本的原则就是<strong>不要在UI线程中执行业务逻辑</strong>. 而现在客户端开发大多使用nw.js/CEF/Electron, 性能有要求的用QT这样的C++框架比较多, C#客户端开发逐渐没落了, 这里汇总了几种C#中异步更新UI的方法, 这些方法中都存在<strong>观察者模式</strong>的思想在里面, 是有一定学习价值的. 另外, <strong>谨以此文纪念那些曾经开发过的C#客户端程序.</strong></p>
<h2 id="通过delegate切换到ui线程调用" tabindex="-1">通过Delegate切换到UI线程调用 <a class="header-anchor" href="#通过delegate切换到ui线程调用" aria-label="Permalink to &quot;通过Delegate切换到UI线程调用&quot;">&ZeroWidthSpace;</a></h2>
<p>上篇介绍了Delegate家族的详细情况. Delegate也是实现异步界面更新的基础. 在UI对象上调用<strong>Invoke(同步)/BeginInvoke(异步)<strong>方法, 并传入一个能够改变UI的</strong>Delegate</strong>, 这是最基础也是最常用的异步更新UI的方法, 其他的方法大多可以认为都是对这种方法的封装.</p>
<blockquote>
<p>Talk is cheap, show me the code.</p>
</blockquote>
<div class="language-csharp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">csharp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Collections</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Generic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ComponentModel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Drawing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Threading</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Windows</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Forms</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TestUI</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //其他类代码省略</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> partial</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Form</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //定义一个更新UI的委托类</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> delegate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UpdateProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> val</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //定义一个委托实例</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UpdateProgress</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> updator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">            InitializeComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //以更新UI的方法作为实例化委托</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">            this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.updator </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UpdateProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(UIUpdateProgress);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            ThreadPool.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">QueueUserWorkItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> WaitCallback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(DoInBackgroundThread));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UIUpdateProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> val</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">            this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.ProgressBar.Value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> val;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DoInBackgroundThread</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">object</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> state</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //如果直接在这个后台线程中调用UIUpdateProgress</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //可能会出现: System.InvalidOperationException</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //线程间操作无效: 从不是创建控件"XXX"的线程访问它。</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                Thread.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                //调用Form对象的Invoke或BeginInvoke方法</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                //传入的委托, 将会在UI线程中执行</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">                this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">BeginInvoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(updator, i);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br></div></div><h2 id="synchronizationcontext实现线程间消息传递" tabindex="-1">SynchronizationContext实现线程间消息传递 <a class="header-anchor" href="#synchronizationcontext实现线程间消息传递" aria-label="Permalink to &quot;SynchronizationContext实现线程间消息传递&quot;">&ZeroWidthSpace;</a></h2>
<p><strong>UI线程</strong>中会有一个SynchronizationContext对象, 允许一个线程和另外一个线程进行通讯, SynchronizationContext在通讯中充当传输者的角色. <strong>SynchronizationContext.Current</strong>能得到当前被主UI线程接管过的SynchronizationContext对象. 在Form1 form = new Form1()之前, SynchronizationContext对象是为空, 而当实例化Form1窗体后, SynchronizationContext对象就被附加到这个线程上了. 这个对象中有一个<strong>Send方法和Post方法</strong>, 都可以用来作为界面更新委托的调用者, Send和Post的区别就像Invoke与BeginInvoke的区别:</p>
<ul>
<li>Send() 是简单的在当前线程上去调用委托来实现（同步调用）. 也就是在子线程上<strong>直接调用UI线程</strong>执行, 等UI线程执行完成后子线程才继续执行.</li>
<li>Post() 是在线程池上去调用委托来实现（异步调用）. 这是子线程会<strong>从线程池中找一个线程</strong>去调UI线程, 子线程<strong>不等待UI线程的完成</strong>而直接执行自己下面的代码.</li>
</ul>
<div class="language-csharp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">csharp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> partial</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Form</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    SynchronizationContext</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> m_SyncContext</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        InitializeComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        m_SyncContext </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SynchronizationContext.Current;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        ThreadPool.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">QueueUserWorkItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> WaitCallback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(DoInBackgroundThread));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UIUpdateProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">object</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> val</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.ProgressBar.Value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)val;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DoInBackgroundThread</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">object</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> state</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Thread.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //调用Post/Send方法</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            m_SyncContext.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Post</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(UIUpdateProgress, i);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><h2 id="使用基于事件机制的backgroundworker" tabindex="-1">使用基于事件机制的BackgroundWorker <a class="header-anchor" href="#使用基于事件机制的backgroundworker" aria-label="Permalink to &quot;使用基于事件机制的BackgroundWorker&quot;">&ZeroWidthSpace;</a></h2>
<p>BackgroundWorker是微软封装好的一个类, 可以通过它来作为一个简单的事件注册中心, <strong>注册发布者(DoWorkEventHandler)<strong>和</strong>订阅者(ProgressChangedEventHandler)</strong>, 在DoWorkEventHandler中调用<strong>ReportProgress</strong>时, 触发ProgressChanged事件通知到订阅者, 对于线程切换和异步回调都是透明的, 是一个比较好的封装. API文档见<a href="https://docs.microsoft.com/zh-cn/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.1" target="_blank" rel="noreferrer">这里</a></p>
<div class="language-csharp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">csharp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> partial</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Form</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BackgroundWorker</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> backgroundWorker</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Form1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        InitializeComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        backgroundWorker </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> BackgroundWorker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        backgroundWorker.WorkerReportsProgress </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        backgroundWorker.DoWork </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DoWorkEventHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(DoInBackgroundThread);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        backgroundWorker.ProgressChanged </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ProgressChangedEventHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(UIUpdateProgress);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        backgroundWorker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RunWorkerAsync</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> UIUpdateProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">object</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sender</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ProgressChangedEventArgs</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.ProgressBar.Value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)e.ProgressPercentage;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DoInBackgroundThread</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">object</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sender</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">DoWorkEventArgs</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Thread.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">50</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            backgroundWorker.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ReportProgress</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br></div></div><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>常见的异步更新界面的方法主要就是这些, 这也是Windows客户端编程的基础. 至于如何在.Net Framework下用WinForm/WPF/UWP写出<strong>非常酷炫的客户端程序</strong>, 请类比这幅《怎样画马》
<img src="//filecdn.code2life.top/draw-horse.jpg" alt="drawHorse"></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[C# Delegate详解]]></title>
            <link>https://code2life.top/blog/0021-csharp-delegate</link>
            <guid>https://code2life.top/blog/0021-csharp-delegate</guid>
            <pubDate>Wed, 04 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="c-delegate详解" tabindex="-1">C# Delegate详解 <a class="header-anchor" href="#c-delegate详解" aria-label="Permalink to &quot;C# Delegate详解&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="delegate的定义及作用" tabindex="-1">Delegate的定义及作用 <a class="header-anchor" href="#delegate的定义及作用" aria-label="Permalink to &quot;Delegate的定义及作用&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="delegate是什么" tabindex="-1">Delegate是什么 <a class="header-anchor" href="#delegate是什么" aria-label="Permalink to &quot;Delegate是什么&quot;">&ZeroWidthSpace;</a></h4>
<p>委托(Delegate)是一个类, 可以将方法当作另一个方法的参数来进行传递. 在C#中, Event, Action, Func, Predicate都是特殊的委托.</p>
<blockquote>
<p>MSDN官方定义 : C#中的委托类似于C或C++中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码，而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同，委托是<span style="color: #ff0000;">面向对象、类型安全的，并且是安全</span>的.</p>
</blockquote>
<h4 id="delegate的特性" tabindex="-1">Delegate的特性 <a class="header-anchor" href="#delegate的特性" aria-label="Permalink to &quot;Delegate的特性&quot;">&ZeroWidthSpace;</a></h4>
<ol>
 	<li>delegate声明都是System.Delegate或System.MulticastDelegate的子类, 相当于把<span style="color: #ff0000;">函数声明成一个类</span>, 这个函数类<span style="color: #ff0000;">的实例就是函数指针地址的封装</span></li><li>delegate类可以通过<span style="color: #ff0000;">new</span>或者<span style="color: #ff0000;">CreateDelegate</span>生成函数实例, 已存在的<span style="color: #ff0000;">相同参数和返回值的函数</span>也是delegate的实例</li><li>delegate类重载了<span style="color: #ff0000;">+= -= !=</span> 操作符, 可以在delegate实例上添加或删除函数</li><li>delegate实例可以直接用<span style="color: #ff0000;">括号调用或用Invoke方法调用</span>, 效果一毛一样.</li><li>delegate实例可以使用<span style="color: #ff0000;">BeginInvoke</span>开始异步调用, <span style="color: #ff0000;">EndInvoke</span>阻塞当前线程至调用结束</li><li>delegate实例可以使用<span style="color: #ff0000;">GetInvocationList</span>方法得到调用队列, <span style="color: #ff0000;">Method</span>属性得到当前调用的函数, <span style="color: #ff0000;">Target</span>属性得到当前调用函数的上下文对象</li>
</ol>
<p>这是一个例子:</p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Collections</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Generic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Linq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> CSharp杂技</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> delegate</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DelegateDeclare</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">T</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Example</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DelegateDeclare</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DelegateTest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TestExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            DelegateTest.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Invoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> FuncInstance1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"static example"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> example</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Example</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> nonStatic</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> NoneStaticExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(example.DelegateTest);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            example.DelegateTest </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DelegateDeclare</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(FuncInstance1);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            example.DelegateTest </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> DelegateDeclare</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(nonStatic.FuncInstance2);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            example.DelegateTest </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">x</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Lambda Expression Example"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">                return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            };</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            example.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">TestExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> NoneStaticExample</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Test</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> " None-Static"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> FuncInstance2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"none static example"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.Test);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br></div></div><h4 id="啥时候用delegate" tabindex="-1">啥时候用Delegate <a class="header-anchor" href="#啥时候用delegate" aria-label="Permalink to &quot;啥时候用Delegate&quot;">&ZeroWidthSpace;</a></h4>
<ol><li>当我们在C#中需要类似<span style="color: #ff0000;">函数指针</span>这样东西时</li><li>当我们需要使用<span style="color: #ff0000;">回调函数</span>的时候</li><li>需要<span style="color: #ff0000;">异步调用</span>的时候</li><li>实现<span style="color: #ff0000;">观察者模式</span>的时候</li><li>处理<span style="color: #ff0000;">事件响应</span>的时候</li></ol>
<h2 id="event与观察者模式" tabindex="-1">Event与观察者模式 <a class="header-anchor" href="#event与观察者模式" aria-label="Permalink to &quot;Event与观察者模式&quot;">&ZeroWidthSpace;</a></h2>
<p>Event是一个阉割版Delegate, 不能在<span style="color: #ff0000;">类外部</span>使用<span style="color: #ff0000;">除了重载的 += -=方法以外的任何成员和方法</span>, 其他与Delegate一毛一样.
<span style="text-decoration: underline;">为什么不能在声明event的类外部使用原来delegate有的方法和成员变量呢?</span>
因为 : event是典型的发布-订阅模式, 声明event的类, 即发布者,也只有发布者,有权利知道有现在哪些订阅者(GetInvocationList), 啥时候触发事件(Invoke); 而<span style="color: #ff0000;">作为订阅者, 应该只能把自己的事件处理方法加上去</span>(+= EventHandler)<span style="color: #ff0000;">或取消订阅事件</span>(-= EventHandler), 其他操作本来就不应该有的, 而Delegate内部这些操作都是public的, 在发布-订阅模式中不符合面向对象原则.故微软搞出了event关键字, 把不该给订阅者的这些方法和成员置为<span style="color: #ff0000;">private</span>, 更符合<span style="color: #ff0000;">语义</span>和<span style="color: #ff0000;">封装</span>原则. 严格意义上, 发布-订阅模式的发布者也是不需要知道有哪些订阅者的, 一个中心化的事件注册可以把发布者和订阅者完全解耦, 发布者只需触发自己的事件即可, 若不深究设计模式的定义, 其实观察者模式与发布订阅模式是类似的, 或者说是相通的.</p>
<p>观察者模式/发布-订阅模式的例子:</p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Collections</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Generic</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Linq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">using</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> System</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Threading</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">namespace</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Csharp杂技</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Publisher</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> event</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> EventHandler</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> PostArticle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> count</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> EmitEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                PostArticle.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Invoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">count);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                Thread.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Sleep</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[] </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubScriber</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Joey"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SubScribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubScriber</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Code2Life"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SubScribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Publisher</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">EmitEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubScriber</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubScriber</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> name;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> void</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SubScribe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            Publisher.PostArticle </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sender</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{0} Get Article : {1}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, Name, value )); </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//此处只能调用+=和-=, 这就是和Delegate唯一的区别</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br></div></div><h2 id="delegate的变种-func-action-predicate" tabindex="-1">Delegate的变种: Func, Action, Predicate <a class="header-anchor" href="#delegate的变种-func-action-predicate" aria-label="Permalink to &quot;Delegate的变种: Func, Action, Predicate&quot;">&ZeroWidthSpace;</a></h2>
<p>Action是<span style="color: #ff0000;">无返回值</span>的泛型委托, 参数在0-16个的Delegate
Func是<span style="color: #ff0000;">有返回值</span>, 参数在0-16个的Delegate
Predicate是<span style="color: #ff0000;">返回值为bool</span>, 参数为一个的Delegate</p>
<p>3行代码解释区别</p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//Action&#x3C;T1,T2,...,T16>也是可以的, 无返回值是关键</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Action</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> actionExample</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"actionExample"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">funcExample</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Int32.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a); };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Predicate</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> predicateExample</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">IsNullOrWhiteSpace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a);};</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>笔者已经用了两年C#了, 由于即将换一份工作, 以后用到它的机会可能不多了. 慢慢的发现多学会一门编程语言或者多学会某一个技术框架其实并没有那么重要, 重要的是学到其中的编程范式、设计思路等更抽象层面上普适的知识. 对于编程语言、技术框架, 存在即合理, 适合的就是最好的. C#的语法的确很优雅, 写起来比Java更舒心, 可惜生态不够繁荣, 开源的太晚了, 如今.Net Core也快速成长, 持续关注吧.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[前端性能优化总结]]></title>
            <link>https://code2life.top/blog/0020-web-perf-improve</link>
            <guid>https://code2life.top/blog/0020-web-perf-improve</guid>
            <pubDate>Sat, 31 Mar 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="前端性能优化总结" tabindex="-1">前端性能优化总结 <a class="header-anchor" href="#前端性能优化总结" aria-label="Permalink to &quot;前端性能优化总结&quot;">&ZeroWidthSpace;</a></h1>
<p>上周在某公司面试, 问了一个看似不难的问题: 如何做前端的性能优化? 当时随便列了七八条简单常用的, 回来细细想想真的是too naive了. 而网络上相关的文章大多是零碎的知识点, 没有分类归纳. 本文分3个部分, 分别介绍<strong>基础通用的Web前端优化</strong>、<strong>前端框架中的优化</strong>、以及对前端性能优化的<strong>总结</strong>.</p>
<h2 id="基础篇" tabindex="-1">基础篇 <a class="header-anchor" href="#基础篇" aria-label="Permalink to &quot;基础篇&quot;">&ZeroWidthSpace;</a></h2>
<p>这一部分总结原生HTML/JS/CSS以及HTTP的优化方法, 基础的前端性能优化是否到位可以使用<a href="https://github.com/googlechrome/lighthouse" target="_blank" rel="noreferrer">lighthouse</a>这个工具来生成网站性能报告</p>
<blockquote>
<p>npm install -g lighthouse
lighthouse <a href="//xxx.xx" target="_blank" rel="noreferrer">//xxx.xx</a></p>
</blockquote>
<h4 id="html标签" tabindex="-1">HTML标签 <a class="header-anchor" href="#html标签" aria-label="Permalink to &quot;HTML标签&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>link标签写在&lt;head&gt;中, script标签写在&lt;body&gt;的最后</li>
<li>link标签可以让浏览器<a href="https://segmentfault.com/a/1190000011065339" target="_blank" rel="noreferrer">预加载资源</a>, rel中写&quot;dns-prefetch/preconnect/prefetch/prerender&quot;</li>
<li>不影响页面渲染的script标签添加defer或async属性延迟加载, 实际上<a href="https://segmentfault.com/q/1010000000640869" target="_blank" rel="noreferrer">defer更好一点</a></li>
<li>减少不必要的DOM嵌套层数</li>
<li>img标签尽量写上width/height属性, 选用合适的图片压缩格式</li>
<li>减少需要请求网络资源的标签, 比如合并CSS/JS</li>
<li>svg和canvas能实现的, 甚至普通CSS就能实现的效果, 就别用img徒增一次网络请求</li>
</ul>
<h4 id="更高效的css" tabindex="-1">更高效的CSS <a class="header-anchor" href="#更高效的css" aria-label="Permalink to &quot;更高效的CSS&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>不要使用CSS import</li>
<li>合理使用CSS选择器, 不同选择器性能差别较大, 少用后代选择器</li>
<li>不要滥用浮动</li>
<li>关键性的CSS可以写入单独的文件放在HTML头部优先渲染</li>
<li>使用CSS Sprites合并图片, CSS中利用background-position定位图片位置</li>
<li>Flexbox布局比传统的CSS布局要更快</li>
<li>CSS3动画是个不错的选择, 但@keyframes的灵活性不如<a href="https://www.cnblogs.com/langzi1989/p/5965818.html" target="_blank" rel="noreferrer">JS动画</a>(js+dom/js+canvas), 顺便Mark<a href="https://popmotion.io/" target="_blank" rel="noreferrer">一个非常6的JS动画库</a></li>
</ul>
<p>关于CSS选择器的性能这里有一张表:</p>
<ul>
<li>ID, e.g. #header</li>
<li>Class, e.g. .promo</li>
<li>Type, e.g. div</li>
<li>Adjacent sibling, e.g. h2 + p</li>
<li>Child, e.g. li &gt; ul</li>
<li>Descendant, e.g. ul a</li>
<li>Universal, i.e. *</li>
<li>Attribute, e.g. [type=&quot;text&quot;]</li>
<li>Pseudo-classes/-elements, e.g. a:hover</li>
</ul>
<h4 id="javascript的写法很大程度上决定了性能" tabindex="-1">JavaScript的写法很大程度上决定了性能 <a class="header-anchor" href="#javascript的写法很大程度上决定了性能" aria-label="Permalink to &quot;JavaScript的写法很大程度上决定了性能&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>现在MVVM框架已经通过<strong>虚拟DOM</strong>尽量减少了DOM操作, 如果写原生的DOM操作也需要注意<strong>减少DOM操作</strong></li>
<li>不要在JavaScript中频繁修改样式, 尤其是会导致<strong>重排</strong>的样式修改</li>
<li>不要在JavaScript主线程中执行耗时的复杂计算, 让<strong>Web Worker</strong>去做复杂的计算</li>
<li>在JavaScript中使用缓存时及时释放, 尽量<strong>不要在闭包中引用到DOM元素</strong></li>
<li>尽量使用浏览器原生的对象和接口实现功能, 如Proxy, Promise等</li>
<li>尽量不要使用with/eval等改变执行上下文的关键字</li>
<li>if短路, 循环尽量提前退出, 递归函数写成尾递归...这些基础编程常识就不多说了</li>
<li>删除DOM节点时, 同时删掉注册在上面的事件</li>
<li>使用<a href="//www.kokojia.com/article/20726.html" target="_blank" rel="noreferrer">BigPipe</a>的思路, 流水线式加载页面, 优先渲染主要部分再到次要部分, 减少用户感受到的延迟</li>
<li><a href="https://www.jianshu.com/p/f5033cec605e" target="_blank" rel="noreferrer">函数柯里化</a>实现懒求值, 减少不必要的逻辑计算</li>
<li><strong>减少事件监听</strong>, 采用<strong>节流和去抖</strong>等方式防止同一个事件被过于频繁的触发</li>
<li><strong>requestAnimationFrame</strong>比setTimeout(f, 0)更好, 也可以用来编写高性能的动画, js+canvas实现动画理论上比改变css实现动画更快, 前提是<strong>不要触发reflow和GC</strong></li>
</ul>
<h4 id="使用缓存" tabindex="-1">使用<a href="https://www.cnblogs.com/chenqf/p/6386163.html" target="_blank" rel="noreferrer">缓存</a> <a class="header-anchor" href="#使用缓存" aria-label="Permalink to &quot;使用[缓存](https://www.cnblogs.com/chenqf/p/6386163.html)&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>HTTP1.0使用Expires请求头标识资源失效时间</li>
<li>HTTP1.1使用Cache-Control请求头标识资源缓存策略</li>
<li>根据修改时间的Last-Modified/If-Modified-Since</li>
<li>根据资源唯一标识的Etag/If-None-Match</li>
</ul>
<h4 id="网络与http" tabindex="-1">网络与HTTP <a class="header-anchor" href="#网络与http" aria-label="Permalink to &quot;网络与HTTP&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>不会变化的静态文件<strong>使用CDN</strong>, 一举多得</li>
<li>单页应用中不要在初次访问时加载所有的文件, <strong>懒加载</strong>当前路由的js/css</li>
<li>Cookie中不要保存太多数据, 及时清除无用的Cookie, 这样可以减小HTTP头的大小</li>
<li>对响应内容<strong>使用gzip</strong>压缩增加了服务器的CPU计算量, 但能减少网络的压力, 大部分情况下更合适</li>
<li>CSS/JS等资源确保已经是<strong>Minify + Uglify</strong>的</li>
<li>HTTPS比HTTP慢, 但更安全. 可以的话, <strong>使用HTTP2</strong>, 并启用<strong>HPACK</strong>压缩HTTP头</li>
<li>HTTPS也有很多配置,<strong>TLS1.2是2-RTT的,TLS1.3就有1-RTT和0-RTT两种模式</strong>, 不同的认证/密钥协商/对称加密算法对性能也有决定性的影响</li>
<li>服务器操作系统级别的<strong>TCP底层配置</strong>优化</li>
<li>尽量减少每个数据帧在网络拓扑中的路径, 如选择合适的数据中心地理位置, <strong>减少代理的次数</strong>等</li>
<li><strong>重定向非常耗时</strong>, 减少重定向的次数</li>
<li>据说IPv6比IPv4更快10%~15%</li>
</ul>
<h4 id="使用新的h5-api" tabindex="-1">使用新的H5 API <a class="header-anchor" href="#使用新的h5-api" aria-label="Permalink to &quot;使用新的H5 API&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li><a href="https://www.jianshu.com/p/62338c038c42" target="_blank" rel="noreferrer">ServiceWorker</a>是独立于当前页面的一段运行在浏览器后台进程里的脚本
<ul>
<li>ServiceWorker缓存资源文件, 在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线</li>
<li>PWA(Progressive Web Apps)应用可以提升用户体验, 让用户在浏览器中获得APP的体验</li>
</ul>
</li>
<li>尽量使用<a href="//www.w3school.com.cn/html5/html5_ref_canvas.asp" target="_blank" rel="noreferrer">Canvas</a>实现2D图形绘制</li>
<li>尽量使用<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API" target="_blank" rel="noreferrer">WebGL</a>实现3D效果, 但貌似也没别的办法实现3D了</li>
</ul>
<h2 id="前端框架篇" tabindex="-1">前端框架篇 <a class="header-anchor" href="#前端框架篇" aria-label="Permalink to &quot;前端框架篇&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="jquery性能优化" tabindex="-1">jQuery性能优化 <a class="header-anchor" href="#jquery性能优化" aria-label="Permalink to &quot;jQuery性能优化&quot;">&ZeroWidthSpace;</a></h4>
<p>jQuery虽然已经随着技术的变更逐渐被人遗忘, 但作为笔者进入前端界的启蒙框架, 还是来回忆一下jQuery代码的正确编写姿势吧. 部分内容摘自<a href="https://www.jb51.net/article/47639.htm" target="_blank" rel="noreferrer">此处</a></p>
<ul>
<li>jQuery很多API是针对DOM的操作, 但仍需减少DOM操作, 如append, before, after等函数不要每拼接一个DOM元素字符串调一次, 放在一起调</li>
<li>可以适当使用原生JS实现的不要用jQuery, 比如$(this).attr(&quot;id&quot;)可以优化成this.id</li>
<li>jQuery选择器的优化也很重要, 不要写过于复杂的选择器, 与CSS选择器可以类比</li>
<li>缓存某个jQuery对象, 使用find获取DOM后代元素, 比每次都写一个$(&quot;XX XXX&quot;)要好</li>
<li>jQuery对象集合是一个类数组, 用for循环数字索引遍历比each更快, 但为了代码可读性更好一般还是用each</li>
<li>由于jQuery1.3之后用Sizzle引擎查询DOM, 选择器应该写成从宽泛到具体, 如: $(&quot;a.link[target='_blank']&quot;)</li>
<li>不要频繁调用$(xx).css('xx', 'xx'), 尽量把要改变的css放到一句话, 使用对象作为参数调用, 如: $(xx).css({'color': 'red', 'font-size': '1.5em'})</li>
<li>$(window).ready(function(){})比$(function (){})长, 推荐使用后者;</li>
</ul>
<h4 id="angularjs-1-x-性能与可维护性的优化" tabindex="-1">AngularJS 1.x 性能与可维护性的优化 <a class="header-anchor" href="#angularjs-1-x-性能与可维护性的优化" aria-label="Permalink to &quot;AngularJS 1.x 性能与可维护性的优化&quot;">&ZeroWidthSpace;</a></h4>
<p>AngularJS 1.x版本现在用的也不多了, 当年也是前端工程化发展过程中一个如日中天的MVVM框架, 也用它做了几个不小的项目. 如今Angular2也发布快一年了, 但JS文件过大, 以及过于先进的Typescript + 注解的语法, 可能并不适合初学者. 这里顺便翻出了我以前做的一些AngularJS笔记, 大部分是对性能有提升的, 或是对代码可维护性有益的, 很切题.</p>
<ul>
<li>不要在html里直接用script标签写controller</li>
<li>在使用双括号绑定时小心加载延迟, 尽量使用ng-bind指令代替, ng-cloak指令可防止双括号语法的绑定可能带来的闪烁</li>
<li>不要使用$.ajax, 使用$http对应的方法代替, $http的方法会返回Promise对象, 注意用success和error两个方法指定处理函数</li>
<li>不要使用jQuery或$符号进行dom操作, 如必须操作dom, 使用angular.element()代替</li>
<li>如果controller比较复杂, 建议将复杂的业务逻辑整合到独立的service中并在controller注入依赖, 否则controller将难以维护</li>
<li>不要在ng指令的表达式中进行复杂的函数调用, 使用filter代替或在controller中事先设置正确的值, 否则容易造成逻辑分散, 性能上对V8引擎也很不友好</li>
<li>从数据出发, 而非视图出发. 设法进行View与ViewModel的双向绑定而非用DOM操作取值设置ViewModel</li>
<li>公共的方法应该封装到公共的service中供其他模块注入</li>
<li>公共的DOM组件应该封装到directive供其他模块注入</li>
<li>不要注入不必要的服务</li>
<li>首次加载不要加载所有js文件, 使用ui-router的延迟加载</li>
<li>使用Angular内置的服务代替常见的操作, 比如$timeout, $log等</li>
<li>尽量不要在$rootScope设置过复杂的对象</li>
<li>$scope.$apply慎用, 这会重新遍历整个DOM来校验双向绑定, $digest大部分情况可以代替$apply</li>
<li>前端校验尽量使用ng-model内置的服务, 比如$dirty, $invalid</li>
<li>Angular内置的一些不太常用的指令却可以解决很多常见的问题, 避免复杂逻辑, 比如 ng-checked, ng-readonly, ng-style等等, 能使用指令解决的问题不要写代码.</li>
<li>所有input标签需要数据绑定的用ng-model指令, 不要在value属性设置值, 而是在$scope内定义ViewModel</li>
<li>$scope内不要平行的定义大量的ViewModel, 存在关联属性的用对象或类封装起来</li>
<li>最后插播一条Angular2的性能优化黑科技: AoT编译以及基于rollup.js的Tree-Shaking.</li>
</ul>
<h4 id="vue-js性能优化" tabindex="-1">Vue.js性能优化 <a class="header-anchor" href="#vue-js性能优化" aria-label="Permalink to &quot;Vue.js性能优化&quot;">&ZeroWidthSpace;</a></h4>
<p>Vue.js的势头早已超过了它的鼻祖AngularJs, 生态逐渐繁荣, 关于Vue.js的性能优化方法, 与AngularJs有很多相通之处, 笔者对Vue.js的理解并不够深入, 此处暂时列这几条吧, 欢迎补充.</p>
<ul>
<li>避免template中过于复杂的表达式, 封装到methods中更好</li>
<li>慎用watch: { deep: true}, watch太多数据或者一个比较大的对象, JS引擎的压力会很大</li>
<li>vue-router的懒加载非常方便, 当一个子页面逻辑复杂时, 没有理由不使用<a href="https://router.vuejs.org/zh-cn/advanced/lazy-loading.html" target="_blank" rel="noreferrer">懒加载</a></li>
<li>除了Vue组件, 尽量少的import/require, 只在需要的时候来加载依赖, 这样可以提升首次渲染的性能, <a href="https://www.npmjs.com/package/webpack-bundle-analyzer" target="_blank" rel="noreferrer">webpack-bundle-analyzer</a>可以用来查看webpack打包后模块的依赖情况</li>
<li>可以在webpack中配置externals, 可以忽略不需要打包的库, 并在index.html中使用cdn地址的script标签加载第三方库</li>
<li>尽量使用v-show代替v-if, 避免不必要的DOM元素创建和删除, 当然v-if做前端权限控制时不能用v-show代替</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">&ZeroWidthSpace;</a></h2>
<p>万变不离其宗, 前端性能优化方法不计其数, 但其思路和原理不外乎下面几种:</p>
<ul>
<li>减少请求的次数和大小</li>
<li>减少重排(Reflow)和重绘(Repaint)</li>
<li>尽量减少事件, 优化JavaScript代码的执行效率</li>
<li>尽量使用浏览器原生提供的特性以及更现代化的技术来实现功能</li>
<li>预加载或懒加载, 通过更高效的用户体验变相提升性能</li>
<li>网络、HTTP、服务端性能的优化</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Docker命令合集]]></title>
            <link>https://code2life.top/blog/0019-docker-commands</link>
            <guid>https://code2life.top/blog/0019-docker-commands</guid>
            <pubDate>Sat, 10 Mar 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="docker命令合集" tabindex="-1">Docker命令合集 <a class="header-anchor" href="#docker命令合集" aria-label="Permalink to &quot;Docker命令合集&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="docker-命令行接口" tabindex="-1">Docker 命令行接口 <a class="header-anchor" href="#docker-命令行接口" aria-label="Permalink to &quot;Docker 命令行接口&quot;">&ZeroWidthSpace;</a></h2>
<p>Docker提供了丰富的CLI用于容器的管理及相关配置, 这里记录一些<strong>常用的命令和参数</strong></p>
<h4 id="运行容器常用参数" tabindex="-1">运行容器常用参数 <a class="header-anchor" href="#运行容器常用参数" aria-label="Permalink to &quot;运行容器常用参数&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>docker run:</strong> 启动一个容器, 如果没有镜像会自动下载镜像, 这是最常用的命令之一, 参数也是最多的</p>
<ul>
<li><strong>-v</strong> 挂载卷 宿主目录:容器目录</li>
<li><strong>-m</strong> 挂载一个文件系统 与-v类似, 实际上大多使用-v参数</li>
<li><strong>-p</strong> 绑定端口 宿主端口:容器端口</li>
<li><strong>--name</strong> 指定容器名称</li>
<li><strong>--rm</strong> 退出后自动删除, 不能与-d同时使用</li>
<li><strong>-d</strong> 后台守护进程模式启动</li>
<li><strong>-it</strong> 分配一个伪终端并开启标准输入实现交互式访问容器(-i interactive, -t tty)</li>
<li><strong>--link</strong> 链接到另一个容器, 在当前容器中写入hosts能够直接访问另一个容器</li>
<li><strong>-m</strong> 限制内存字节数</li>
<li><strong>-c</strong> 限制cpu的使用率</li>
<li><strong>-e</strong> 从外部导入容器的环境变量</li>
<li><strong>-a</strong> 与主机关联标准输入输出</li>
<li><strong>--network</strong> 指定网络堆栈, 除默认docker0网桥外, 可以通过docker network create自定义网络</li>
</ul>
<h4 id="运行容器示例" tabindex="-1">运行容器示例 <a class="header-anchor" href="#运行容器示例" aria-label="Permalink to &quot;运行容器示例&quot;">&ZeroWidthSpace;</a></h4>
<p><strong>在交互式伪终端命令行中启动一个busybox, busybox是一个精简的微型linux系统, 包括了几百个命令行工具, 非常小.</strong></p>
<blockquote>
<p>docker run --name busybox -it --rm busybox</p>
</blockquote>
<p><strong>启动mongodb容器示例, 挂载/data/db用于存储BSON数据, 同时暴露27017端口, --bind_ip_all是mongod的启动参数, 让mongodb对外部可见</strong></p>
<blockquote>
<p>docker run --name mongodb -d -p 27017:27017 -v /home/mongo_data:/data/db  mongo mongod --bind_ip_all</p>
</blockquote>
<p><strong>使用/bin/bash作为入口命令交互式的启动一个node.js容器</strong></p>
<blockquote>
<p>docker run -it --name node_cmd --rm node /bin/bash</p>
</blockquote>
<p><strong>一般启动应用的命令</strong></p>
<blockquote>
<p>docker run --name application1 -d -p 80:80 -v --link mysql /home/docker_application1:/data</p>
</blockquote>
<h4 id="容器管理" tabindex="-1">容器管理 <a class="header-anchor" href="#容器管理" aria-label="Permalink to &quot;容器管理&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>
<p><strong>docker ps</strong>: 查询容器</p>
<ul>
<li>-a 查询所有容器, 包括非运行状态的</li>
<li>-q 只查询ID</li>
</ul>
</li>
<li>
<p><strong>docker rm</strong>: 删除某个或某些容器(-f 参数强制删除)</p>
</li>
<li>
<p><strong>docker rmi</strong>: 删除某个或某些镜像(-f 参数强制删除)</p>
</li>
<li>
<p><strong>docker images</strong>: 查询本地镜像列表</p>
</li>
<li>
<p><strong>docker logs</strong>: 查询某个容器的日志</p>
</li>
<li>
<p><strong>docker start/stop/restart</strong>: 不言而喻</p>
</li>
<li>
<p><strong>docker attch</strong>: 与run命令-a参数一样, 重定向容器的标准输入输出</p>
</li>
<li>
<p><strong>docker search</strong>: 在仓库中搜索一个镜像</p>
</li>
<li>
<p><strong>docker info</strong>: 查询Docker运行状态</p>
</li>
<li>
<p><strong>docker network/volume/image/container</strong> 管理网络/卷/镜像/容器.这些用的不多, --help自行查询了</p>
</li>
<li>
<p><strong>docker inspect</strong> 检查一个容器的详细情况, 定位问题时常用</p>
</li>
<li>
<p><strong>docker stats</strong> 检查容器对cpu和内存的使用情况</p>
</li>
<li>
<p>停止所有的容器</p>
<blockquote>
<p>docker stop -f $(docker ps -a -q)</p>
</blockquote>
</li>
<li>
<p>删除所有的容器</p>
<blockquote>
<p>docker rm -f $(docker ps -a -q)</p>
</blockquote>
</li>
</ul>
<h2 id="dockerfile与镜像构建" tabindex="-1">Dockerfile与镜像构建 <a class="header-anchor" href="#dockerfile与镜像构建" aria-label="Permalink to &quot;Dockerfile与镜像构建&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="镜像定制" tabindex="-1">镜像定制 <a class="header-anchor" href="#镜像定制" aria-label="Permalink to &quot;镜像定制&quot;">&ZeroWidthSpace;</a></h4>
<p>在实际使用过程中, 或者CI/CD的流程中, 需要制作包含应用程序的镜像. Docker提供了很多用于修改制作镜像的命令, 以及基于Dockerfile的镜像构建机制</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Dockerfile 编写好之后, 就可以执行build命令构建了</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 也可以指定一个名称, 标签, 指定一个包含Dockerfile的目录执行构建镜像</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># --rm用于构建完之后删除临时中间镜像</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --rm</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name:tag</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dir4Dockerfile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#再附加 -m --cpu-xxx 参数还可以设置内存CPU配额</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 在容器中执行一条命令</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> exec</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -it</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> mkdir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /data</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 查看当前镜像发生了哪些变化</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> diff</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> container</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 本地提交镜像的更改, 与Git非常类似</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 提交修改, 提交后docker images能看到新的镜像, 可以运行使用</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --author</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "xx"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --message</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "xxx"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> namespace/image:tag</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 将本地提交的镜像推送到注册中心对应的镜像仓库中</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 上传修改, 默认上传到了docker.io, 在cloud.docker.com中登录后可以看到,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 经过几分钟后DockerHub也可以搜索到, 可通过docker login 不同账号上传到私有仓库中</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> push</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> namespace/image:tag</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><h4 id="dockerfile示例" tabindex="-1">Dockerfile示例 <a class="header-anchor" href="#dockerfile示例" aria-label="Permalink to &quot;Dockerfile示例&quot;">&ZeroWidthSpace;</a></h4>
<p>Dockerfile是构建镜像的基础, 它的语法比较简单, 主流的编辑器都能找到对应的插件, 这里Mark几个链接</p>
<ul>
<li><a href="//www.docker.org.cn/dockerppt/114.html" target="_blank" rel="noreferrer">Dockerfile语法</a></li>
<li><a href="//www.jb51.net/article/115327.htm" target="_blank" rel="noreferrer">Dockerfile编写的最佳实践</a></li>
<li><a href="https://www.cnblogs.com/lienhua34/p/5170335.html" target="_blank" rel="noreferrer">Dockerfile编写及ENTRYPOINT和CMD的区别</a></li>
</ul>
<p>这是一个常见的Dockerfile的格式</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">FROM</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> image:tag</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LABEL</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> maintainer</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "xx@xx.com"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WORKDIR</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 可以有多个,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> RUN之间可以切换</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ENV</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> VARIABLE</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> XXX</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">...</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ARG</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ARGUMENT=XXXX</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">VOLUME</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dir2</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  声明挂载点</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">COPY</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Dockerfile外部的文件</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dir3</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ADD</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  自解压,普通外部文件,URL都可以</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /dir4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RUN</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Command</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RUN</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Command</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">...</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">RUN</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Command</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">EXPOSE</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> port</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">HEALTHCHECK</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> CMD</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --fail</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> //localhost:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$APP_PORT </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> exit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ENTRYPOINT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"XXX.sh"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 用户启动容器时指定的命令会覆盖此命令, 只能写一条作为默认执行的命令</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 也可以不指定命令, 指定数据作为ENTRYPOINT的参数</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">CMD</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"XXX"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"XXX"]</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><h4 id="dockerfile编写建议" tabindex="-1">Dockerfile编写建议 <a class="header-anchor" href="#dockerfile编写建议" aria-label="Permalink to &quot;Dockerfile编写建议&quot;">&ZeroWidthSpace;</a></h4>
<p>我把<a href="//www.jb51.net/article/115327.htm" target="_blank" rel="noreferrer">这篇编写Dockerfile最佳实践</a>里面的一些重点摘录了下来, 理解了容器的运行原理和镜像构建原理之后, 也就很容易理解为什么这样做更好了</p>
<ul>
<li>编写.dockerignore文件</li>
<li>容器只运行单个应用</li>
<li>将多个RUN指令合并为一个</li>
<li>基础镜像的标签不要用latest</li>
<li>每个RUN指令后删除多余文件</li>
<li>选择合适的基础镜像(alpine版本最好)</li>
<li>设置WORKDIR和CMD</li>
<li>使用ENTRYPOINT (可选)</li>
<li>在entrypoint脚本中使用exec</li>
<li>COPY与ADD优先使用前者</li>
<li>合理调整COPY与RUN的顺序</li>
<li>设置默认的环境变量，映射端口和数据卷</li>
<li>使用LABEL设置镜像元数据</li>
<li>添加HEALTHCHECK</li>
</ul>
<h2 id="附录" tabindex="-1">附录 <a class="header-anchor" href="#附录" aria-label="Permalink to &quot;附录&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="安装docker命令" tabindex="-1">安装Docker命令 <a class="header-anchor" href="#安装docker命令" aria-label="Permalink to &quot;安装Docker命令&quot;">&ZeroWidthSpace;</a></h4>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>yum install -y yum-utils device-mapper-persistent-data lvm2</span></span>
<span class="line"><span>yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo</span></span>
<span class="line"><span>yum install docker-ce</span></span>
<span class="line"><span></span></span>
<span class="line"><span>systemctl enable docker</span></span>
<span class="line"><span>systemctl start docker</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h4 id="centos7-etc-docker-daemon-json-修改国内镜像地址" tabindex="-1">CentOS7 /etc/docker/daemon.json 修改国内镜像地址 <a class="header-anchor" href="#centos7-etc-docker-daemon-json-修改国内镜像地址" aria-label="Permalink to &quot;CentOS7 /etc/docker/daemon.json 修改国内镜像地址&quot;">&ZeroWidthSpace;</a></h4>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>{</span></span>
<span class="line"><span>"registry-mirrors": ["https://kuamavit.mirror.aliyuncs.com", "https://registry.docker-cn.com", "https://docker.mirrors.ustc.edu.cn"], </span></span>
<span class="line"><span>"max-concurrent-downloads": 10,</span></span>
<span class="line"><span>"log-driver": "json-file",</span></span>
<span class="line"><span>"log-level": "warn",</span></span>
<span class="line"><span>"log-opts": {</span></span>
<span class="line"><span>    "max-size": "10m",</span></span>
<span class="line"><span>    "max-file": "3"</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><h2 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h2>
<p>当前比较前沿的<strong>微服务架构</strong>以及<strong>DevOps</strong>需要一系列高效率的生产工具和更现代化的技术路线来支撑, <strong>容器化</strong>无疑是当前最被广泛认可的优秀的基础技术之一. 这篇和上篇把Docker一些基础的知识梳理了一遍, 更深层次的东西等以后在实践中慢慢领悟了.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Docker技术闲谈]]></title>
            <link>https://code2life.top/blog/0018-docker-concepts</link>
            <guid>https://code2life.top/blog/0018-docker-concepts</guid>
            <pubDate>Thu, 01 Mar 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="docker技术闲谈" tabindex="-1">Docker技术闲谈 <a class="header-anchor" href="#docker技术闲谈" aria-label="Permalink to &quot;Docker技术闲谈&quot;">&ZeroWidthSpace;</a></h1>
<p>初次接触Docker是两年前, 那时候还没有docker-ce/docker-ee的区分, 叫docker-io, 稍微摸索了一下感觉这个技术确实是可以大幅度提高生产效率的, 当时国内各大云服务商开始提供Docker服务, 除了BAT, 还有网易蜂巢, Daocloud等等优秀的容器服务提供商, 可谓方兴未艾.</p>
<p>但可惜我所在的公司并不重视容器化和微服务这块, 两年过去了并没有在实践中用上这把利剑, 现在Docker的生态已经繁荣昌盛, 除了docker-compose, docker-swarm等相关组件, kubernetes也逐渐走向成熟. 本文先复习一下Docker相关的知识, 温故知新, 保持学习, 有机会就实践.</p>
<h4 id="docker概述" tabindex="-1">Docker概述 <a class="header-anchor" href="#docker概述" aria-label="Permalink to &quot;Docker概述&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Docker提供了在称为容器的<strong>松散隔离环境</strong>中<strong>打包和运行应用程序</strong>的能力. 隔离和安全性允许您在给定主机上同时运行多个容器. 容器是轻量级的, 因为它们不需要管理程序的额外负载, 而是直接在主机的<strong>内核</strong>中运行. 这意味着, 与使用虚拟机相比, 您可以在给定的硬件组合上运行更多的容器. 你甚至可以在实际上是虚拟机的主机中运行Docker容器.</p>
</blockquote>
<p>Docker提供工具和平台来管理容器的生命周期：</p>
<ul>
<li>容器能够开发应用程序的运行时及其支持组件.</li>
<li>容器可以作为分发和测试应用的单元.</li>
<li>使用容器在不同环境中部署应用是无差别的.</li>
</ul>
<p>Docker的作用和应用场景在于它简化了开发生命周期, 容器非常适合持续集成和持续交付（CI / CD）工作流程. 记得以前在Docker官网上的一句名言**&quot;Build once, run anywhere!&quot;<strong>, 后来变成了</strong>&quot;Build, Ship, and Run Any App, Anywhere!&quot;**, 这句话就是对Docker作用的最佳答案. 当你为了一个庞大的系统部署到测试环境、预上线环境、生产环境、XXX环境耗费大量人力的时候, 当你为了寻找一个问题为什么总是在生成环境上出现的时候, 当你维护大量部署脚本找不到头绪的时候, 当你为了应用水平扩展和负载均衡费劲周折的时候, Docker就是救星了, 甚至不需要出动Docker全家桶, 几行Docker命令就能轻松解决!</p>
<p>下面盗了官网的图来说明Docker的逻辑结构(第一个图)和使用结构(第二个图)
<img src="//filecdn.code2life.top/docker-structure1.png" alt="1">
<img src="//filecdn.code2life.top/docker-structure2.svg" alt="2"></p>
<h4 id="镜像" tabindex="-1">镜像 <a class="header-anchor" href="#镜像" aria-label="Permalink to &quot;镜像&quot;">&ZeroWidthSpace;</a></h4>
<p>Dockerfile中的每条命令都会在文件系统中创建一个新的层次结构(layer)，文件系统在这些层次上构建起来，镜像就构建于这些联合的文件系统之上. 所以本质上镜像是一种文件结构. 每一个layer有一个SHA1哈希码, 镜像的变化会反映在layer的增量更新上, 镜像可以修改再提交, 提交镜像就像是一条Git提交记录, 不过提交的对象不是代码, 而是操作系统的状态变化. 一个镜像就相当于一个操作系统的快照, 这些快照根据命名空间、名称、标签组织起来并由仓库管理版本. 镜像可以是包含了一个应用的运行时环境如Ubuntu+JRE+tomcat, 或是一个服务组件如mysql数据库, 也可以只包含一个操作系统如debian等. 一个镜像的大小一般只有几百兆, 并且layer的复用会带来更神奇的效果, 实际拉取镜像的时候可能只要下载几十兆甚至几兆. 镜像也能够通过Docker Hub或类似这样一个镜像注册中心, 提供给任何需要的人.</p>
<h4 id="镜像仓库" tabindex="-1">镜像仓库 <a class="header-anchor" href="#镜像仓库" aria-label="Permalink to &quot;镜像仓库&quot;">&ZeroWidthSpace;</a></h4>
<p>仓库是集中存放镜像文件的场所，仓库注册服务器（Registry）上往往存放着多个仓库，每个仓库中又包含了多个镜像，每个镜像有不同的标签（tag）. 目前，最大的公开仓库是 Docker Hub，存放了数量庞大的镜像供用户下载. 我们可以使用官方提供的<a href="https://hub.docker.com/_/registry/" target="_blank" rel="noreferrer">registry镜像</a>来搭建一个镜像注册中心, 现在更主流的做法是用<a href="https://github.com/vmware/harbor" target="_blank" rel="noreferrer">开源项目harbor</a>搭建企业级的镜像仓库.</p>
<h4 id="容器" tabindex="-1">容器 <a class="header-anchor" href="#容器" aria-label="Permalink to &quot;容器&quot;">&ZeroWidthSpace;</a></h4>
<p>容器是从镜像创建的运行实例. 它可以被启动、开始、停止、删除. 每个容器都是相互隔离的、保证安全的平台. 可以把容器看做是一个简易版的Linux环境，Docker利用容器来运行应用. 说白了镜像和容器的关系，就是类与示例的关系. Docker的命令行工具大多都是对容器做管理的</p>
<h4 id="网络" tabindex="-1">网络 <a class="header-anchor" href="#网络" aria-label="Permalink to &quot;网络&quot;">&ZeroWidthSpace;</a></h4>
<p>Docker的网络子系统是可插拔的，使用驱动程序. Docker自带几个驱动程序，并提供核心网络功能. Docker提供了很多种网络驱动, 默认是<strong>bridge模式</strong>，适用于不同的应用场景, 更改网络设置通过&quot;docker network create&quot;，下面是每种网络驱动的简述：</p>
<ul>
<li><strong>bridge</strong>：<strong>默认的网络驱动程序</strong>. 应用程序运行在需要通信的独立容器中时，通常会使用桥接网络，打个比方就是同一个主机的容器直接有一座桥，容器直接可以直接通信，但主机外看不到这座桥. 这个桥是通过iptables的NAT功能实现的，<a href="//dockone.io/article/458" target="_blank" rel="noreferrer">这里</a>有详述.</li>
<li><strong>host</strong>：对于独立容器，删除容器和Docker主机之间的网络隔离，并直接使用主机的网络. host 仅适用于Docker 17.06及更高版本的群集服务.</li>
<li><strong>overlay</strong>：覆盖网络将多个Docker守护进程连接在一起，并使群集服务能够相互通信. 主流的覆盖网络组件有:<strong>Flannel</strong>, weave等.kubernetes集群网络通信的基础就是覆盖网络, 这又是一个复杂庞大的话题. 以后再谈, 这里<a href="https://www.cnblogs.com/kevingrace/p/6859114.html" target="_blank" rel="noreferrer">Mark</a>一篇文章</li>
<li><strong>macvlan</strong>：Macvlan网络允许为容器分配MAC地址，使其显示为网络上的物理设备。Docker守护进程通过其MAC地址将流量路由到容器。</li>
<li><strong>none</strong>: 不需要网络</li>
</ul>
<p>如何选择?</p>
<ul>
<li>需要多个容器在同一个Docker主机上进行通信时，用户定义的网桥是最好的.</li>
<li>当网络堆栈不需要与Docker主机隔离而又需要隔离运行环境时，主机网络是最好的.</li>
<li>当需要运行在不同Docker主机上的容器进行通信时，或者当多个应用程序使用群集服务一起工作时，覆盖网络是最好的.</li>
<li>Macvlan网络最适合从虚拟机设置迁移或需要容器看起来像网络上的物理主机，每个物理主机都有一个唯一的MAC地址.</li>
<li>当上面的网络驱动都不符合要求时, 也可以选用第三方网络插件</li>
</ul>
<p>以前没有关注过Docker网络的设计和实现, 回过头来看受益颇多, <a href="https://success.docker.com/article/docker-reference-architecture-designing-scalable-portable-docker-container-networks" target="_blank" rel="noreferrer">这里</a>是官网上对Docker网络架构的详述</p>
<h4 id="卷存储" tabindex="-1">卷存储 <a class="header-anchor" href="#卷存储" aria-label="Permalink to &quot;卷存储&quot;">&ZeroWidthSpace;</a></h4>
<p>卷(volume)在Docker中相当于容器的数据盘, 它的用法<a href="https://docs.docker.com/storage/volumes/#start-a-container-with-a-volume" target="_blank" rel="noreferrer">这里</a>有文档, 是非常常用的参数, 如果没有指定存储卷, Docker会在/var/lib/docker/volumes中自动创建一个GUID命名的卷用于容器的文件读写. 实际使用过程中按照一定规则将主机上的目录挂载到容器中更加便于管理.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Node.js进阶] 子进程与Cluster]]></title>
            <link>https://code2life.top/blog/0017-nodejs-childprocess-and-cluster</link>
            <guid>https://code2life.top/blog/0017-nodejs-childprocess-and-cluster</guid>
            <pubDate>Sat, 17 Feb 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="node-js进阶-子进程与cluster" tabindex="-1">[Node.js进阶] 子进程与Cluster <a class="header-anchor" href="#node-js进阶-子进程与cluster" aria-label="Permalink to &quot;[Node.js进阶] 子进程与Cluster&quot;">&ZeroWidthSpace;</a></h1>
<p>node.js的官方文档非常详细, 这里概括性的整理一些常用的和重要的地方, 完整的方法、参数等还是要参考<a href="//nodejs.cn/api/" target="_blank" rel="noreferrer">官方文档</a>. 另外, 本文还记录一个基于cluster模块封装的生产环境上必备的守护进程模块<a href="https://github.com/Unitech/pm2" target="_blank" rel="noreferrer">pm2</a>的使用</p>
<h2 id="node-js创建子进程及进程间通信" tabindex="-1">Node.js创建子进程及进程间通信 <a class="header-anchor" href="#node-js创建子进程及进程间通信" aria-label="Permalink to &quot;Node.js创建子进程及进程间通信&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="核心函数spawn" tabindex="-1">核心函数spawn <a class="header-anchor" href="#核心函数spawn" aria-label="Permalink to &quot;核心函数spawn&quot;">&ZeroWidthSpace;</a></h4>
<p>child_process模块可以赋予node.js创建子进程的能力, 其中包括以下几个函数:</p>
<ul>
<li>exec(command[, options][, callback])</li>
<li>execFile(file[, args][, options][, callback])</li>
<li>fork(modulePath[, args][, options])</li>
<li>spawn(command[, args][, options])</li>
<li>execSync / execFileSync / spawnSync 等用于创建阻塞的同步进程</li>
</ul>
<p>其中<strong>spawn</strong>函数的作用是: <strong>启动一个进程来执行命令</strong>, 其他几个方法如fork, exec等都是对spawn的封装, 更加方便使用.
比如这个例子使用spawn执行了一个bat批处理文件, 并且演示了常用的一些进程间通信的方式</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">spawn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child_process'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> readline</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'readline'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> rl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> readline.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">createInterface</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  input:process.stdin,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  output:process.stdout</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">rl.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">question</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'父进程命令行输入:'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  rl.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">close</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> ps</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> spawn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'spawn.bat'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'arg1'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  cwd: __dirname,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  env: { arg2: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'env args in chiild process'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //in, out, error</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //inherit, pipe, ignore</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  stdio: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'inherit'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'pipe'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'pipe'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/*设置ignore后无法监听stderr*/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ps.stdout.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(data.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果对stdio[2]设置了ignore将会报错:</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//Cannot read property 'on' of null</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ps.stderr.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`错误：${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">data</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//listeners: close, disconnect, error, exit, message</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ps.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'close'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">code</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`子进程退出码：${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">code</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br></div></div><p>上面这段代码, <strong>在当前目录作为工作目录的情况下, 创建了一个执行spawn.bat的进程, 同时传入了一个叫做&quot;arg2&quot;的环境变量, 再将stdin重定向为父进程的输入, stdout和stderr输出流使用管道&quot;输送&quot;到父进程中, 并监听子进程标准输出的&quot;data&quot;事件, 以及进程的退出事件</strong>. 信息量不少, spawn常见的用法都囊括其中了. 其中stdio的配置很灵活, inherit表示重定向到父端, pipe是默认值, ignore表示忽略子进程标准输入输出对应的文件描述符(FD), stdio配置的数组的前三个表示stdin, stdout, stderr, inherit相当于重定向到ChildProcess对象的stdin/stdout/stderr, 数组后面还可以添加自定义的字符串用作进程间通信的命名管道, 其值为'pipe', 'ipc', 'ignore', null, Stream之一.<br>
下面是spawn.bat的脚本代码.</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">@echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> off</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">rem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> input</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stdin=</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">rem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> output</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> %stdin%</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> %1%</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">rem</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 父进程传入的环境变量</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> %arg2%</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">exit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><h4 id="exec-execfile-fork" tabindex="-1">exec/execFile/fork <a class="header-anchor" href="#exec-execfile-fork" aria-label="Permalink to &quot;exec/execFile/fork&quot;">&ZeroWidthSpace;</a></h4>
<p>这三兄弟都是对spawn的封装, 官方文档是这样描述的:</p>
<ul>
<li>exec:  衍生一个 shell，然后在 shell 中执行 command，且缓冲任何产生的输出</li>
<li>execFile: 不衍生 shell, 而是指定的可执行的 file 被直接衍生为一个新进程</li>
<li>fork: 专门用于衍生新的 Node.js 进程, 返回一个<a href="//nodejs.cn/api/child_process.html#child_process_child_process" target="_blank" rel="noreferrer">ChildProcess对象</a></li>
</ul>
<p>由此可以看出它们的区别和联系, 如下</p>
<ul>
<li>exec相比spawn, 会缓冲输出直到子进程结束, 不会持续产生&quot;data&quot;事件</li>
<li>execFile与exec很像, 但由于它不启动shell执行命令, 而是直接运行一个可执行文件, 从而效率更高</li>
<li>fork与前两者关系不大, 是一种用于衍生Node进程的特殊spawn, 并且还封装了一个IPC管道, 用于进程间通信</li>
</ul>
<p>奉上一些示例代码:</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">execFile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">spawn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child_process'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//spawn</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> cp</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> spawn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'dir'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, [], {});</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">cp.stdout.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">   console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(data.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//exec</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'ls -il'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">stdout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">stderr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(stdout);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//execFile</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">execFile</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'echo.bat'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'argv'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">stdout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">stderr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(stdout);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//fork</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> sub</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./child.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sub.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'message'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">m</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">handle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'父进程收到消息:'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, m);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sub.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">send</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({ hello: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'world'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> });</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br></div></div><p>其中fork的示例, 在child.js中通过process监听message事件与父进程通信, 以下是child.js的代码</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'message'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">m</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'子进程收到消息：'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, m);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//进程通信会序列化再还原, NaN序列化后变成null</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//父进程输出 { foo: 'bar', baz: null }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">send</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({ foo: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, baz: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">NaN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> });</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h4 id="进程间通信" tabindex="-1">进程间通信 <a class="header-anchor" href="#进程间通信" aria-label="Permalink to &quot;进程间通信&quot;">&ZeroWidthSpace;</a></h4>
<p>抛开Node.js, 进程间通信的方式有很多, <strong>管道、命名管道、消息队列、共享存储、信号量、套接字、信号等等</strong>. 在child_process模块中使用的主要是管道和命名管道的方式</p>
<blockquote>
<p>管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系.管道一般用于两个不同进程之间的通信.当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式. 命名管道也是一种有名字的先进先出队列(FIFO),但是它允许无亲缘关系进程间的通信.</p>
</blockquote>
<p>同一台设备上的进程间通信, 不管是管道还是队列, 亦或是信号量, Unix Domain Socket, 本质上都是在访问共享的一块内存区域. 进程间通信是一个很复杂的话题, 每种方式有它的优势和缺点, 此处不再展开. 在Node.js中使用spawn()传入配置 <strong>stdio: 'ipc'</strong> 或者用fork()自带的以默认文件描述符<strong>NODE_CHANNEL_FD</strong>通信都能达到目标.</p>
<h2 id="cluster模块详解" tabindex="-1">Cluster模块详解 <a class="header-anchor" href="#cluster模块详解" aria-label="Permalink to &quot;Cluster模块详解&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>cluster模块就是child_process和net模块的组合应用. cluster启动时, 会在内部启动一个TCP服务器, 在cluster.fork()子进程时, 将这个TCP服务器端的socket对应的文件描述符发送给工作进程, 如果工作进程中存在listen()侦听网络端口的调用, 它将拿到该文件描述符, 通过SO_REUSEADDR端口重用, 从而实现多个子进程共享端口</p>
</blockquote>
<p>这段话摘自《深入浅出Node.js》,说明了cluster端口重用的工作原理, 用代码来说明大概是这样的:</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> child1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child_process'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> child2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child_process'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'child.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> server </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'net'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">createServer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这样fork出来的不会端口冲突, 谁抢占到socket谁上</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">server.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">listen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1337</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    child1.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">send</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'server'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, server);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    child2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">send</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'server'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, server);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    server.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">close</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><h4 id="集群-的创建和事件监听" tabindex="-1">&quot;集群&quot;的创建和事件监听 <a class="header-anchor" href="#集群-的创建和事件监听" aria-label="Permalink to &quot;&quot;集群&quot;的创建和事件监听&quot;">&ZeroWidthSpace;</a></h4>
<p>这里的集群打了引号, 是因为cluster模块并不能创建大规模集群, 只是通过多进程来提高对多核设备的硬件利用率, 以此进一步提高应用性能的一种方案. 真正的高可用大规模集群搭建和cluster模块并没有多大关系, 后面会写一些使用kubernetes和容器技术来搭建真正集群的文章. Node.js中cluster的创建和事件监听是这样的:</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> cluster</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'cluster'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> http</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'http'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//调度算法配置, 其他的基础配置在cluster.settings对象中</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">cluster.schedulingPolicy </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> cluster.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">SCHED_RR</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//SCHED_NONE</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 可监听的事件有: setup/fork/online/listening/message/disconnect/exit</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 在cluster上监听或workers中监听都可以</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">cluster.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'listening'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">worker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">address</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    `A worker is now connected to ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">address</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">address</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}:${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">address</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">port</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (cluster.isMaster) {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 跟踪 http 请求</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> numReqs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  setInterval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`numReqs = ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">numReqs</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 计算请求数目</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> messageHandler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">msg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (msg.cmd </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> msg.cmd </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'notifyRequest'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      numReqs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 启动 worker 并监听包含 notifyRequest 的消息</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> numCPUs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'os'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cpus</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">().</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> numCPUs; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    cluster.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> cluster.workers) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    cluster.workers[id].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'message'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, messageHandler);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // Worker 进程有一个http服务器</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  http.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Server</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">req</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">writeHead</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">end</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'hello world</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 通知 master 进程接收到了请求</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">send</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({ cmd: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'notifyRequest'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">listen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">8000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br></div></div><h4 id="worker进程的调度" tabindex="-1">worker进程的调度 <a class="header-anchor" href="#worker进程的调度" aria-label="Permalink to &quot;worker进程的调度&quot;">&ZeroWidthSpace;</a></h4>
<p>上面的示例中, cluster有一个schedulingPolicy的配置, <strong>Round-Robin</strong>和<strong>抢占式</strong>, Windows默认的是<strong>SCHED_NONE</strong>, 也就是让操作系统自己调度; *nix默认的是<strong>SCHED_RR</strong>, 即轮叫调度. 官方文档对于调度的说明是这样的:</p>
<blockquote>
<p>除Windows外的所有操作系统中，SCHED_RR都是默认设置。只要libuv可以有效地分发IOCP handle，而不会导致严重的性能冲击的话，Windows系统也会更改为SCHED_RR。cluster.schedulingPolicy 可以通过设置NODE_CLUSTER_SCHED_POLICY环境变量来实现。这个环境变量的有效值包括&quot;rr&quot; 和 &quot;none&quot;</p>
</blockquote>
<p>这里提到了Node在Windows下采用IOCP实现异步, 顺便解释一下IOCP</p>
<blockquote>
<p>IOCP是Windows有一种独有的内核异步IO方案。IOCP的思路是真正的异步I/O方案，调用异步方法，然后等待I/O完成通知。IOCP内部依旧是通过线程实现，不同在于这些线程由系统内核接手管理。</p>
</blockquote>
<p>libuv屏蔽了不同平台下异步调用的实现, *nix中没有IOCP, libuv是直接对多线程进行封装实现异步的(并非epoll), 操作系统的差异体现在cluster模块上就是调度的不确定性. <a href="https://zhuanlan.zhihu.com/p/27069865" target="_blank" rel="noreferrer">这里</a>有一篇写的不错的文章, 结论是由于Node.js的IPC管道性能和负载均衡不太靠谱, 提到了使用多个独立的Node进程 + nginx upstream负载比直接使用cluster更优, 后面有空来做一个验证.</p>
<h2 id="pm2模块实践" tabindex="-1">PM2模块实践 <a class="header-anchor" href="#pm2模块实践" aria-label="Permalink to &quot;PM2模块实践&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>PM2 is a General Purpose Process Manager and a Production Runtime for Node.js apps with a built-in Load Balancer.</p>
</blockquote>
<p>翻译一下就是: &quot;PM2是一个内置负载均衡的Node.js中通用进程管理器以及生产环境运行时&quot;</p>
<p>PM2模块用起来很简单, 两行就可以在不改变任何代码的情况下, 建立一个Node集群</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> pm2</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -g</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pm2</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> start</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxx.js</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p><img src="//filecdn.code2life.top/pm2-cmd.jpg" alt="pm2">
其他常用的命令如stop/restart/kill/logs/monit等也非常简洁易用, 并且还能集成<a href="https://keymetrics.io/" target="_blank" rel="noreferrer">keymetrics</a>打造非常酷炫的监控, 集成过程只要一行命令</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 注册后的key</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pm2</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> link</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> yw3v85q0c7nl8rt</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> zmxyvh2vuy05dbp</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>效果如下
<img src="//filecdn.code2life.top/pm2-dashboard.png" alt="pm2">
<img src="//filecdn.code2life.top/pm2-monit.jpg" alt="pm2">
监控的UI确实不错, 而且pm2不仅适用于Nodejs, 其他服务和应用也可以. 但是说了这些优点, pm2也有一些缺点, 比如无法管理多台服务器上的Node进程, 共享Socket带来的性能损失等. 我个人感觉pm2<strong>适用于小型应用的生产环境</strong>, 分布式的中大型应用选用成熟的组件更加靠谱, 比如上面提到的nginx或haproxy做负载, 应用和服务器监控使用grafana/zabbix等等, 或者使用<a href="https://alinode.aliyun.com/" target="_blank" rel="noreferrer">alinode</a>的一站式服务. <strong>毕竟pm2还是基于cluster模块的二次封装, cluster做不好的, pm2有同样的局限性.</strong></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Oracle常用SQL及性能优化汇总]]></title>
            <link>https://code2life.top/blog/0016-oracle-sql</link>
            <guid>https://code2life.top/blog/0016-oracle-sql</guid>
            <pubDate>Sun, 11 Feb 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>本文记录一些作为DBA或数据库开发角色所要了解的Sql语句和关于Oracle的知识技巧</p>
</blockquote>
<p><a href="//blog.csdn.net/yangshangwei/article/details/52449489" target="_blank" rel="noreferrer">这个地方</a>有非常专业详细的Oracle调优可能用到的Sql</p>
<h2 id="实用sql合集" tabindex="-1">实用SQL合集 <a class="header-anchor" href="#实用sql合集" aria-label="Permalink to &quot;实用SQL合集&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="查看一些基础参数" tabindex="-1">查看一些基础参数 <a class="header-anchor" href="#查看一些基础参数" aria-label="Permalink to &quot;查看一些基础参数&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看最大允许的连接数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">show parameter processes;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看当前连接数(加上ACTIVE是当前并发的连接)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> count</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">session</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> where</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> status=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'ACTIVE'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看数据库用户</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> all_users;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看数据库版本</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> banner </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> sys</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">v_</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">$</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h4 id="查询表空间" tabindex="-1">查询表空间 <a class="header-anchor" href="#查询表空间" aria-label="Permalink to &quot;查询表空间&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- oracle中表空间相当于物理存储的位置, 每个用户都会指定一个表空间</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 当表空间不足时会带来很多问题, 配置足够的表空间非常重要</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">tablespace_name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "sum MB"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "used MB"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> /</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1024</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "free MB"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       round</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(((</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"used%"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tablespace_name, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes) bytes</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">          from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dba_data_files</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">         group by</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tablespace_name) a,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tablespace_name, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes) bytes, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">max</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bytes) largest</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">          from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dba_free_space</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">         group by</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tablespace_name) b</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> where</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">tablespace_name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">tablespace_name</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> order by</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ((</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">bytes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">desc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><h4 id="挂载新的表空间" tabindex="-1">挂载新的表空间 <a class="header-anchor" href="#挂载新的表空间" aria-label="Permalink to &quot;挂载新的表空间&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 将该dbf文件挂载到当前数据库,以200M为步长自动扩展最大8G</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> database</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> datafile </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'D:\NEW_TABLE_SPACE.dbf'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> autoextend </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">on</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> 200m maxsize 8192m</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h4 id="查询当前执行的事务或sql" tabindex="-1">查询当前执行的事务或SQL <a class="header-anchor" href="#查询当前执行的事务或sql" aria-label="Permalink to &quot;查询当前执行的事务或SQL&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 在排查事务执行慢的原因时非常有用, 可以看到当前执行的sql处于何种等待事件</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sid</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">serial</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">#, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">event</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_fulltext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">username</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">machine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">terminal</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">program</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">executions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">spid</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">direct_writes</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">session</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> WHERE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> status</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ACTIVE'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) s</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  LEFT JOIN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$sqlarea a</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    ON</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_id</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> INNER JOIN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$process p</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    ON</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">paddr</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">addr</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 正在执行的SQL</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sid</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> oracleID,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">username</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> 登录Oracle用户名,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">serial</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">#,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       spid 操作系统ID,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       paddr,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">       sql_text 正在执行的SQL,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">       b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">machine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> 计算机名</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$process a, v$</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">session</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b, v$sqlarea c</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">WHERE</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">addr</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">paddr</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   AND</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_hash_value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> c</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">hash_value</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><h4 id="干掉一个死锁的sql会话" tabindex="-1">干掉一个死锁的SQL会话 <a class="header-anchor" href="#干掉一个死锁的sql会话" aria-label="Permalink to &quot;干掉一个死锁的SQL会话&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- alter ssytem kill session 'sid,serial#';</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 这个比较常用, 解决掉某个被锁住的会话, 下面这句话可以查询死锁会话</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'alter system kill session '''</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> sid</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ','</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> serial</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"># </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ''';'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Deadlock"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">session</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> WHERE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> sid</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> IN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> sid</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$lock </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">WHERE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> block</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h4 id="侦查一下有哪些人在删数据" tabindex="-1">侦查一下有哪些人在删数据 <a class="header-anchor" href="#侦查一下有哪些人在删数据" aria-label="Permalink to &quot;侦查一下有哪些人在删数据&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 使用Hist视图查询最近三天执行过哪些删除语句</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- dba_hist_sqltext的command_type字段有以下常见的几种类型</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 2 insert; 3 select; 6 update; 7 delete;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 42 alter session; 44 commit; 47 begin...end; 48 SET TRANSACTION;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 49 alter system; 85 truncate table;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> c</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">username</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">         a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">program</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">         b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">         b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">command_type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">         a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sample_time</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dba_hist_active_sess_history a</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">         JOIN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dba_hist_sqltext b</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            ON</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sql_id</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">         JOIN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dba_users c</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            ON</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">user_id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> c</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">user_id</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   WHERE</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">     a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sample_time</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> BETWEEN</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> SYSDATE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> AND</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> SYSDATE</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">         AND</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">command_type</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> IN</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">85</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">ORDER BY</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sample_time</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> DESC</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h4 id="如何删库跑路" tabindex="-1">如何删库跑路 <a class="header-anchor" href="#如何删库跑路" aria-label="Permalink to &quot;如何删库跑路&quot;">&ZeroWidthSpace;</a></h4>
<p>Oracle中并没有提供类似Mysql中的<strong>drop database</strong>语句, 删起库来很不方便, 只能自己动手拼接字符串来删库跑路了</p>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 这些语句分别从user_tables ... user_triggers 等表中</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 选择出数据库的表,序列,存储过程,触发器等, 拼接drop实现获取删除语句</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop table '</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> table_name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">' cascade constraints PURGE;'</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">13</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> user_tables;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop sequence '</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sequence_name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">';'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">13</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> user_sequences;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop procedure '</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> object_name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">';'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">13</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">chr(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> user_objects </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">where</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> object_type</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'PROCEDURE'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'drop TRIGGER "'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sys_context(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'USERENV'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'CURRENT_USER'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'"."'</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> trigger_name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'";'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">CHR(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">13</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">CHR(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> user_triggers;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><h2 id="性能调优" tabindex="-1">性能调优 <a class="header-anchor" href="#性能调优" aria-label="Permalink to &quot;性能调优&quot;">&ZeroWidthSpace;</a></h2>
<p>Oracle数据库的调优是一门很深的学问, 此处先记录一些非常<strong>简单有效</strong>的优化方法, 高端的调优方法还是得找专业DBA人员.
在定位问题时, 除了查询v$xxx表获得信息外, 生成分析AWR性能报告也是个好办法, <a href="//blog.itpub.net/26954807/viewspace-1300697/" target="_blank" rel="noreferrer">这里</a>有一个关于AWR报告如何分析的文章</p>
<h4 id="进程数、最大连接数配置" tabindex="-1">进程数、最大连接数配置 <a class="header-anchor" href="#进程数、最大连接数配置" aria-label="Permalink to &quot;进程数、最大连接数配置&quot;">&ZeroWidthSpace;</a></h4>
<p>上面记录了查看最大连接数的办法<strong>show parameter processes</strong>, 修改的办法自然是用<strong>alter system</strong>语句了</p>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 最大并发连接设置为300</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> processes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 300</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scope </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> spfile;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h4 id="sga-pga配置" tabindex="-1">SGA/PGA配置 <a class="header-anchor" href="#sga-pga配置" aria-label="Permalink to &quot;SGA/PGA配置&quot;">&ZeroWidthSpace;</a></h4>
<p>SGA/PGA是oracle内存结构的重要部分, 配置的参数直接影响了oracle可用的内存大小, 相关的各种内存池的大小, <a href="//blog.itpub.net/25264937/viewspace-694917/" target="_blank" rel="noreferrer">这里</a>有一个关于oracle内存结构的文章.</p>
<ul>
<li><strong>SGA大小</strong>: 对于一个纯数据库系统, 应该分配<strong>80%的总内存</strong>, 剩下的给操作系统即可, 数据库+应用的系统适当减少SGA大小</li>
<li><strong>PGA大小</strong>: 一般的OLTP应用, 分配**SGA的20%**即可, 如果有很多大查询, 适当增加PGA的大小, 但不要超过SGA的50%</li>
</ul>
<div class="language-sql vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 进入sqlplus命令行</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">sqlplus sys</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/password</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> as</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sysdba</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看数据库参数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">show parameter sga;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">show parameter pga;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">show parameter workarea;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 修改SGA(System Global Area)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sga_max_size</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">4096M scope</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">spfile;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> sga_target</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">4096M scope</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">spfile;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">shutdown</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> immediate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">startup;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 修改PGA(Program Global Area)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> workarea_size_policy</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=auto</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scope</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=both</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pga_aggregate_target</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">512m scope</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=both</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 10g后db_cache_size自动管理, 非专业人士不要乱改</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> db_cache_size</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">1024M  scope</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">spfile </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">sid=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'数据库SID'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">-- 查看字典缓冲区的使用率, 低于90%的增加shared_pool_size</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(gets</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">getmisses</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">usage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">fixed))</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">sum</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(gets) </span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Data dictionary cache"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v$rowcache;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">alter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> system</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> SHARED_POOL_SIZE </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> 64M;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><h4 id="sql和表结构优化" tabindex="-1">SQL和表结构优化 <a class="header-anchor" href="#sql和表结构优化" aria-label="Permalink to &quot;SQL和表结构优化&quot;">&ZeroWidthSpace;</a></h4>
<p>除了数据库本身的配置, 表的设计和SQL本身也是影响执行效率的决定性因素. 大部分情况下, 数据库的瓶颈都是人为造成的, 但大多也都归咎于硬件太差/数据量过大/网络不好等因素, 但在甩锅的时候, 也应该考虑一下这些可能提升性能的地方是否都已经做到了.</p>
<ul>
<li><strong><a href="//blog.itpub.net/7450001/viewspace-911115/" target="_blank" rel="noreferrer">索引</a></strong>
<ul>
<li>成也索引, 败也索引, 以牺牲少量的写入性能换取读取性能的极大提高, SQL优化利器</li>
<li>索引的类型有B-Tree索引, 位图索引, 函数索引等等. 选择索引的类型/字段要视业务而定, 这里有一些<a href="https://www.cnblogs.com/downey/p/5302088.html" target="_blank" rel="noreferrer">通用的原则</a></li>
</ul>
</li>
<li><strong>并行</strong>
<ul>
<li>开启并行: <strong>alter session enable parallel dml;</strong></li>
<li>并行查询(table可选, n为并行度): select /<em>+PARALLEL([table], n)</em>/ * from table</li>
</ul>
</li>
<li><strong>避免低效的写法</strong>
<ul>
<li>大部分情况下, insert时大量values批量插入比执行n条insert语句快好几个数量级</li>
<li>merge into比 insert into ... select快, 比 update 快一个数量级</li>
<li>select的字段中嵌套select非常危险</li>
<li>in不一定比exists更快, in只支持1000个以下</li>
<li>union/union all可以代替两次select, union有时候还能代替去重</li>
<li>存储过程一般比较快, 但不到万不得已不要用, 调试和维护的成本太高</li>
<li>truncate比delete更快</li>
<li>越底层的库/框架越快, Mybatis没有JDBC快, Dapper没有ADO.NET快, ADO.NET没有ODP.NET快, 至于EntityFramework只能呵呵了</li>
<li>...</li>
</ul>
</li>
<li><strong><a href="https://www.cnblogs.com/adolfmc/p/5381737.html" target="_blank" rel="noreferrer">物理存储上分库分区分表</a></strong>
<ul>
<li>范围分区/Hash分区/复合分区</li>
<li>读写分离</li>
</ul>
</li>
<li><strong>表设计</strong>
<ul>
<li>CHAR比VARCHAR更快, VARCHAR比*LOB更快, 视实际情况选择合适的字段</li>
<li>只增不删或很少删的表可以使用nologing, 配合**/<em>+append</em>/高水位线插入**提升效率, 可以使用在table或insert语句上, <a href="https://www.cnblogs.com/softidea/p/5336741.html" target="_blank" rel="noreferrer">这里有详述</a></li>
<li>表结构尽量符合第三范式减少不必要的冗余</li>
<li>...</li>
</ul>
</li>
</ul>
<p>SQL的优化又是可以写一整本书的事情了, 这里列举的几个有的是Oracle特有的, 有的是所有RDBMS通用的, 此文不再深入讨论.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Http协议升级之路]]></title>
            <link>https://code2life.top/blog/0015-https-http2-tls</link>
            <guid>https://code2life.top/blog/0015-https-http2-tls</guid>
            <pubDate>Mon, 05 Feb 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="http协议升级之路" tabindex="-1">Http协议升级之路 <a class="header-anchor" href="#http协议升级之路" aria-label="Permalink to &quot;Http协议升级之路&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>几乎所有Web相关的技术都离不开其传输协议HTTP, 纵观HTTP本身的发展历程, 从简单到复杂, 再到一个完备的体系, HTTP在不断完善的期间如何解决各种问题的方式, 非常值得学习</p>
</blockquote>
<h2 id="http-0-9-http-1-0" tabindex="-1">HTTP 0.9 HTTP 1.0 <a class="header-anchor" href="#http-0-9-http-1-0" aria-label="Permalink to &quot;HTTP 0.9 HTTP 1.0&quot;">&ZeroWidthSpace;</a></h2>
<p>HTTP建立之初, 主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器. 但随着技术的发展, HTTP所做的事情已经远远不止于传输html这样简单, HTTP基于TCP协议, TCP建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间逐渐成为了用户体验和服务器性能的绊脚石.基于HTTP的特性, 能够优化的主要在这三个方面, 后面HTTP的发展和优化主要也是从这些方面着手的, 尽可能多的同时加载资源, 尽可能少的重建TCP连接.</p>
<ul>
<li>DNS查询: 发送请求的域名需要通过DNS解析,缓存DNS信息能够减少查询DNS服务器的时间</li>
<li>浏览器阻塞: 对于同一个域名, 浏览器一般都有限制策略, 同时请求的资源过多时, 后续请求会阻塞排队等候</li>
<li>TCP连接: HTTP1.0的TCP连接是不能复用的, 每次请求都会建立连接, 关于TCP连接的基础知识见下图<br>
<img src="//filecdn.code2life.top/tcp-conn.png" alt="tcp"></li>
</ul>
<h2 id="http-1-1" tabindex="-1">HTTP 1.1 <a class="header-anchor" href="#http-1-1" aria-label="Permalink to &quot;HTTP 1.1&quot;">&ZeroWidthSpace;</a></h2>
<p>HTTP1.1在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议. 它与HTTP1.0的主要区别主要体现在下面几点.</p>
<ul>
<li>缓存处理, 在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准, HTTP1.1则引入了更多的缓存控制策略例如Entity tag机制, If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略.</li>
<li>带宽优化及网络连接的使用, HTTP1.0中, 存在一些浪费带宽的现象, 例如客户端只是需要某个对象的一部分, 而服务器却将整个对象送过来了, 并且不支持断点续传功能, HTTP1.1则在请求头引入了range头域, 它允许只请求资源的某个部分, 即返回码是206（Partial Content）, 这样就方便了开发者自由的选择以便于充分利用带宽和连接.</li>
<li>错误通知的管理, 在HTTP1.1中新增了24个错误状态响应码, 如409（Conflict）表示请求的资源与资源的当前状态发生冲突；410（Gone）表示服务器上的某个资源被永久性的删除.</li>
<li>Host头处理, 在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址, 因此, 请求消息中的URL并没有传递主机名（hostname）.但随着虚拟主机技术的发展, 在一台物理服务器上可以存在多个虚拟主机（Multi-homed Web Servers）, 并且它们共享一个IP地址.HTTP1.1的请求消息和响应消息都应支持Host头域, 且请求消息中如果没有Host头域会报告一个错误（400 Bad Request）.</li>
<li>长连接, HTTP 1.1支持长连接（PersistentConnection）和请求的流水线（Pipelining）处理, 在一个TCP连接上可以传送多个HTTP请求和响应, 减少了建立和关闭连接的消耗和延迟, 在HTTP1.1中默认开启Connection： keep-alive, 一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点.</li>
</ul>
<p>但是HTTP1.1仍然存在一些问题, 比如明文传输, 每次都会传输一个相对较大的头, 长连接带来的服务端压力等等, 针对HTTP安全性的问题, HTTPS出现了.</p>
<h2 id="https" tabindex="-1">HTTPS <a class="header-anchor" href="#https" aria-label="Permalink to &quot;HTTPS&quot;">&ZeroWidthSpace;</a></h2>
<p>HTTP协议运行在TCP之上, 所有传输的内容都是明文, HTTPS运行在SSL/TLS之上, SSL/TLS运行在TCP之上, 所有传输的内容都经过加密的. <a href="https://www.cnblogs.com/timdes1/p/8306840.html" target="_blank" rel="noreferrer">这里</a>有一篇关于SSL/TLS协议的深入分析, 看完之后感觉自己对HTTPS的理解太皮毛了, 这里概括的划下重点.</p>
<p>TLS大致是由3个组件拼成的：</p>
<ol>
<li>对称加密传输组件,例如aes-128-gcm</li>
<li>认证密钥协商组件,例如rsa-ecdhe</li>
<li>密钥扩展组件,例如TLS-PRF-sha256</li>
</ol>
<p>组件可以再拆分为5类算法,在TLS中,这5类算法组合在一起,称为一个CipherSuite：</p>
<ul>
<li>authentication (认证算法)</li>
<li>encryption (加密算法 )</li>
<li>message authentication code (消息认证码算法 简称MAC)</li>
<li>key exchange (密钥交换算法)</li>
<li>key derivation function （密钥衍生算法)</li>
</ul>
<p><strong>TLS设计了一个算法协商过程,来允许加入新的算法</strong><br>
例如下面这样一行CipherSuite意义是 :<br>
ECHDE密钥交换, RSA做认证, AES-128-gcm做对称加密
其中由于GCM属于AEAD加密模式, 无需单独使用Mac算法</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p><strong>认证密钥协商</strong>与<strong>对称加密传输</strong>构成了TLS的通信协议层, 而大部分对SSL/TLS的文章中多是认证密钥协商的阐述, 而对称加密传输过程很少涉及, 经过一些浅显的了解, 我发现其实对称加密传输时, 远远不止是用交换的密钥和一个对称加密算法(AES, RC4[这家伙已经被破解不能用了])加密解密这样简单, 这个过程需要分片/重组、压缩(有漏洞的)、加密解密、HMAC(Hash-based Message Authentication Code)验证. 不同的加密模式差别很大, 以GCM为例, GCM模式是AEAD(关联数据的认证加密)一种, 是一种Encrypt-then-MAC模式, 关于密码模式的分类这里有详细解释<a href="https://zh.wikipedia.org/zh-cn/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F#%E5%85%B6%E5%AE%83%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%AF%86%E7%A0%81%E5%AD%A6%E6%A6%82%E5%BF%B5" target="_blank" rel="noreferrer">wiki</a>
这些加密模式需要用到一个很重要的参数即初始化向量IV, 必须用密码学安全的伪随机数生成器(CSPRNG)生成, 另外, 现在主流的AEAD模式不需要MAC key, 需要一个增长序列(nonce)参数, 这些参数通过组合散列等方式扩展密钥, 用于加密传输. 在TLS1.3中, 密钥扩展使用<a href="https://en.wikipedia.org/wiki/HKDF" target="_blank" rel="noreferrer">HKDF</a>算法, 更加安全.</p>
<p>而认证密钥协商的过程就是握手建立连接的过程, TLS1.2与TLS1.3差别比较大, 但主要思路就是通过非对称加密算法, 来实现客户端和服务端的身份确认(数字证书), 并交换一个用于此次会话的基础密钥.
基于大数分解的RSA算法和基于椭圆曲线的DH算法,不断发展为3类非对称算法:</p>
<ul>
<li>非对称加密 RSAES-PKCS1-v1_5,RSAES-OAEP, Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5 等</li>
<li>非对称密钥协商 DH,DHE,ECDH,ECDHE 等</li>
<li>非对称数字签名：RSASSA-PKCS1-v1_5,RSASSA-PSS, ECDSA, DSA, ED25519 等</li>
</ul>
<p>现在主流的做法是使用RSA算法做身份认证, DH算法做密钥交换, DH在交换密钥时, 客户端和服务端无需直接传送密钥, 而是根据DH参数各自计算出来真正的密钥, 比RSA更安全.
这里盗了几张图, 分别是RSA做密钥交换时的序列图(AES256-GCM-SHA256)和DH做密钥交换的序列图(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)<br>
RSA算法做密钥交换
<img src="//filecdn.code2life.top/rsa.png" alt="rsa">
<img src="//filecdn.code2life.top/rsa_seq.png" alt="rsa">
<a href="//blog.csdn.net/fangxin205/article/details/54707633" target="_blank" rel="noreferrer">DH算法</a>做密钥交换
<img src="//filecdn.code2life.top/dh_rsa.png" alt="dh">
<img src="//filecdn.code2life.top/dh_rsa_seq.png" alt="dh">
TLS1.3目前已经逐渐普及, 例如github在客户端已经禁用TLS1.2, 导致低版本的git无法使用. TLS1.3重大的改动主要有:</p>
<ul>
<li>0-RTT支持, 1-RTT握手支持 (TLS1.2是2-RTT的)</li>
<li>改为使用HKDF做密钥扩展</li>
<li>彻底禁止RC4</li>
<li>彻底禁止压缩</li>
<li>彻底禁止aead以外的其他算法</li>
<li>去除aead的显式IV</li>
<li>去除了AEAD的AD中的长度字段</li>
<li>去除ChangeCipherSpec</li>
<li>去除重协商</li>
<li>去除静态RSA和DH密钥协商</li>
</ul>
<h2 id="插曲-ssl-strip攻击" tabindex="-1">插曲 SSL Strip攻击 <a class="header-anchor" href="#插曲-ssl-strip攻击" aria-label="Permalink to &quot;插曲 SSL Strip攻击&quot;">&ZeroWidthSpace;</a></h2>
<p>SSL剥离攻击是一种中间人劫持实现的降级攻击, 具体实现如下
<a href="https://www.jianshu.com/p/983d43b4ba1e" target="_blank" rel="noreferrer">如何进行一次完整的 SSLStrip 攻击</a><br>
相对于各种TLS算法漏洞和算法降级攻击, 这种粗暴的HTTPS剥离为HTTP攻击算是比较简单的, 当然防御的办法也很简单, 就是<strong>HSTS</strong>
HSTS的全称是HTTP Strict-Transport-Security, 它是一个Web安全策略机制, 用来强制进行Https通信, 表现在浏览器上就是一个响应头</p>
<div class="language- vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>Strict-Transport-Security: &#x3C;max-age=>[; includeSubDomains][; preload]</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>添加这个相应头, 就可以告诉浏览器,在接下来的xxx时间内,对于当前域名及其子域名的后续通信应该强制性的只使用HTTPS,直到超过有效期为止.</p>
<p>但HSTS存在一个比较薄弱的环节, 那就是浏览器没有当前网站的HSTS信息的时候, 或者第一次访问网站的时候, 依然需要一次明文的HTTP请求和重定向才能切换到HTTPS, 以及刷新HSTS信息。而就是这么一瞬间却给攻击者留下了可乘之机, 使得他们可以把这一次的HTTP请求劫持下来, 继续中间人攻击.</p>
<p>解决这个隐患的办法就是<strong>Preload List</strong></p>
<blockquote>
<p>浏览器里内置一个列表, 只要是在这个列表里的域名, 无论何时、何种情况, 浏览器都只使用HTTPS发起连接.这个列表由Google Chromium维护, FireFox、Safari、IE等主流浏览器均在使用. 有一个正确的证书和Https站点, 就能将域名申请到这个列表中, 申请地址在<a href="https://hstspreload.org" target="_blank" rel="noreferrer">这里</a></p>
</blockquote>
<h2 id="http2-0" tabindex="-1">HTTP2.0 <a class="header-anchor" href="#http2-0" aria-label="Permalink to &quot;HTTP2.0&quot;">&ZeroWidthSpace;</a></h2>
<p>2012年Google提出了SPDY的方案, 采用带优先级的多路复用、Header压缩、服务端推送等多种技术手段, 大幅度提升了页面加载速度, 但之后大部分浏览器只支持HTTPS协议之上的SPDY协议, 实际上对HTTP协议并没有兼容, 后来HTTP2.0出现了, 在SPDY的思路上又做了一些改进, 并兼容HTTP协议. 这里列举了几个HTTP2.0的特性, 大部分是与SPDY类似的</p>
<ul>
<li>二进制分帧层 HTTP1.x基于文本, 2.0使用二进制格式, 最小单位为帧, 能够交错并行发送请求和响应, 解决了HTTP1.x的阻塞问题, HTTP2.0中, 超过2的14次方-1（16383）字节的数据就会分帧发送</li>
<li>多路复用 由于引入了一个二进制分帧/重组层, 自然而然的实现了多路复用, 每个请求有一个ID, 并附带优先级, 这样缓解了HTTP1.x为了提高性能建立多个TCP长连接带来的服务端压力</li>
<li>Header压缩 HTTP2.0使用了<a href="https://www.jianshu.com/p/f44b930cfcac" target="_blank" rel="noreferrer">HPACK算法</a>减少HTTP头的大小, 相比SPDY采用的<a href="https://baike.baidu.com/item/DEFLATE/9650075?fr=aladdin" target="_blank" rel="noreferrer">DEFLATE压缩算法</a>更加符合HTTP协议的应用场景</li>
<li>服务端推送 当请求一个资源时, 服务器会顺便把关联的资源一并发送过来, 当浏览器需要使用时会发现一切尽在缓存中, 得来全不费工夫啊, 这里是一个<a href="//www.ruanyifeng.com/blog/2018/03/http2_server_push.html" target="_blank" rel="noreferrer">Nginx实现推送资源的配置例子</a>, nodejs中也有<a href="https://github.com/spdy-http2/node-spdy" target="_blank" rel="noreferrer">SPDY模块</a>用来创建一个HTTP2/SPDY服务器.</li>
</ul>
<h2 id="http3-0" tabindex="-1">HTTP3.0 <a class="header-anchor" href="#http3-0" aria-label="Permalink to &quot;HTTP3.0&quot;">&ZeroWidthSpace;</a></h2>
<p>HTTP2.0已经有很多优化了，但遇到了新的瓶颈，高速可靠网络中TCP的优势反而成了劣势：</p>
<ul>
<li>TCP的有序性，单个TCP长连接会有排头阻塞问题</li>
<li>TCP的可靠性，接收方需要不断发送ACK才能让滑动窗口继续滑下去</li>
<li>TCP拥塞控制机制，慢启动和拥塞避免等等传统TCP拥塞算法，导致不能高效利用带宽</li>
</ul>
<p>HTTP3.0目前还没有正式发布， 一个简单的理解可以是 HTTP3.0 = HTTP2.0 + QUIC</p>
<p>QUIC是何许人也？全称Quick UDP Internet Connection，递归写法就是QUIC了，是Google发起的一种基于UDP的可靠传输协议，因为UDP不可靠（不仅有协议本身的问题，另外UDP的无连接性导致NAT设备无法确认连接关闭状态，ISP丢包等等问题），Google的天才们想了很多办法让UDP可靠了，搭配HTTP2.0就是目前所说的HTTP3.0草案。目前HTTP3.0用的极少，这里不再展开。这里也有一篇关于HTTP发展到3.0的文章，Mark一下：<a href="https://mp.weixin.qq.com/s/fC10Cyj6xjjwOCnqxX-Dvg" target="_blank" rel="noreferrer">https://mp.weixin.qq.com/s/fC10Cyj6xjjwOCnqxX-Dvg</a></p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Linux入门] 常用命令和Vim操作汇总]]></title>
            <link>https://code2life.top/blog/0014-linux-shell-command</link>
            <guid>https://code2life.top/blog/0014-linux-shell-command</guid>
            <pubDate>Tue, 30 Jan 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="linux入门-常用命令和vim操作汇总" tabindex="-1">[Linux入门] 常用命令和Vim操作汇总 <a class="header-anchor" href="#linux入门-常用命令和vim操作汇总" aria-label="Permalink to &quot;[Linux入门] 常用命令和Vim操作汇总&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>本文收录一些常用的linux运维命令和基础知识, 持续更新, 不常用的命令和参数自行用man命令查阅或在此<a href="//man.linuxde.net/" target="_blank" rel="noreferrer">网址://man.linuxde.net/</a>下查询</p>
</blockquote>
<h2 id="常用文件位置" tabindex="-1">常用文件位置 <a class="header-anchor" href="#常用文件位置" aria-label="Permalink to &quot;常用文件位置&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="打开终端预执行的脚本位置" tabindex="-1">打开终端预执行的脚本位置 <a class="header-anchor" href="#打开终端预执行的脚本位置" aria-label="Permalink to &quot;打开终端预执行的脚本位置&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># shell登录</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/etc/profile</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/etc/profile.d/*.sh</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/root/.bash_profile</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/root/.bashrc</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/etc/bashrc</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 非shell登录时只会执行这几个</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/root/.bashrc</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/etc/bashrc</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">/etc/profile.d/a</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>常见的做法是编辑/username/.bashrc文件, 写入自定义的命令之后, 再使用
<strong>source /username/.bashrc</strong>命令使之生效, 其中source命令是用来在非shell环境中执行脚本的.</p>
<h4 id="常用软件的配置位置" tabindex="-1">常用软件的配置位置 <a class="header-anchor" href="#常用软件的配置位置" aria-label="Permalink to &quot;常用软件的配置位置&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>nginx: /etc/nginx/nginx.conf</li>
<li>docker: /etc/docker/daemon.json</li>
<li>network:
<ul>
<li>[debian系]/etc/network/interfaces</li>
<li>[red-hat系]/etc/sysconfig/network-script/ifcfg-xxx</li>
</ul>
</li>
<li>vsftp: /etc/vsftpd/vsftpd.conf
<ul>
<li><a href="//filecdn.code2life.top/vsftpd.conf" target="_blank" rel="noreferrer">VsFtp配置好的例子</a></li>
</ul>
</li>
<li>pam: /etc/pam.d/*
<ul>
<li><a href="//v.colinlee.fish/posts/pam-tutorial-1-intro.html" target="_blank" rel="noreferrer">Linux认证模块: PAM介绍和配置</a></li>
</ul>
</li>
</ul>
<h4 id="内核与系统组件相关文件位置" tabindex="-1">内核与系统组件相关文件位置 <a class="header-anchor" href="#内核与系统组件相关文件位置" aria-label="Permalink to &quot;内核与系统组件相关文件位置&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>用户密码: /etc/shadow /etc/passwd
<ul>
<li>/etc/passwd列格式 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell</li>
<li>/etc/shadow列格式 登录名:加密口令:最后一次修改时间:最小时间间隔:最大时间间隔:警告时间:不活动时间:失效时间:标志</li>
</ul>
</li>
<li>host: /etc/hosts /etc/hostname</li>
<li>cron: /etc/crontab /etc/cron.d/*</li>
<li>进程信息: /proc/pid</li>
<li>负载信息: cat /proc/loadavg</li>
<li>默认PATH:
<ul>
<li>/usr/local/sbin</li>
<li>/usr/local/bin</li>
<li>/usr/sbin</li>
<li>/usr/bin</li>
<li>/sbin /bin</li>
</ul>
</li>
<li>DNS: /etc/resolv.conf</li>
<li>DHCP: /etc/dhclient.conf</li>
<li>文件系统: /etc/fstab [可配合fdisk命令查看分区信息]
<ul>
<li>软驱装配点 /floppy /mnt/floppy /media/floppy</li>
<li>光驱装配点 /cdrom /mnt/cdrom /media/cdrom</li>
</ul>
</li>
<li>内核信息: /proc/version</li>
<li>红帽系selinux: /etc/selinux/config</li>
</ul>
<h2 id="日常运维命令" tabindex="-1">日常运维命令 <a class="header-anchor" href="#日常运维命令" aria-label="Permalink to &quot;日常运维命令&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="文件-进程-网络管理" tabindex="-1">文件/进程/网络管理 <a class="header-anchor" href="#文件-进程-网络管理" aria-label="Permalink to &quot;文件/进程/网络管理&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>last/lastb 查看登录/登录失败的历史</li>
<li>top 查看实时进程资源占用</li>
<li>tload 显示系统的负载情况</li>
<li>w 显示当前登录的用户信息</li>
<li>ps -aux 查看进程列表</li>
<li>free -m 查看内存使用情况</li>
<li>setenforce 0 临时关闭selinux</li>
<li>du -sh 查看当前目录占用大小</li>
<li>df -lh 查看本地磁盘使用情况</li>
<li>du -h --max-depth=3 /dir 递归查询/dir中3层以内的目录所有文件的大小</li>
<li>du -cks * | sort -rn | head -n 10 查找前10个最大 的文件或目录</li>
<li>rm /var/cache/apt/archives/lock &amp;&amp; rm /var/lib/dpkg/lock Ubuntu下解除dpkg的锁</li>
<li>fdisk -l 查看磁盘分区情况</li>
<li>uname -a / lsb_release -a 查看内核/发行版信息</li>
<li>lsof -i:80 查看占用80端口的进程</li>
<li>netstat -anp 配合grep查看占用端口的进程</li>
<li>ss -aA tcp / ss -lnt / ss -ltp /ss -s 查看socket端口详情和统计</li>
<li>iptables -L -n 查看iptables配置</li>
<li>date -s &quot;20180101 15:30:30&quot; 设置时间,date命令查看时间</li>
<li>ntpdate -u ntp.api.bz 与授时服务器同步时间</li>
<li>一波Combo直接杀掉名称匹配字符串的进程</li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ps</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -aux</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">grep</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nginx'.*master'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">awk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -F</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ' '</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $2}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sed</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '2d'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">xargs</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -9</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><ul>
<li>一波Combo直接杀掉占用端口的进程</li>
</ul>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">lsof</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> :80</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> awk</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -F</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ' '</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '{print $2}'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> sed</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '1d'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xargs</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> kill</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -9</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h2 id="grep-sed-awk-专题" tabindex="-1">grep | sed | awk 专题 <a class="header-anchor" href="#grep-sed-awk-专题" aria-label="Permalink to &quot;grep | sed | awk 专题&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="待更新" tabindex="-1">待更新 <a class="header-anchor" href="#待更新" aria-label="Permalink to &quot;待更新&quot;">&ZeroWidthSpace;</a></h4>
<h2 id="vim的正确打开方式" tabindex="-1">vim的正确打开方式 <a class="header-anchor" href="#vim的正确打开方式" aria-label="Permalink to &quot;vim的正确打开方式&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="_6种模式及其状态转换" tabindex="-1">6种模式及其状态转换 <a class="header-anchor" href="#_6种模式及其状态转换" aria-label="Permalink to &quot;6种模式及其状态转换&quot;">&ZeroWidthSpace;</a></h4>
<ol>
<li>普通模式</li>
</ol>
<ul>
<li>打开vim默认的模式</li>
<li>能够进行光标移动删除等各种命令</li>
</ul>
<ol start="2">
<li>插入模式</li>
</ol>
<ul>
<li>按a/i进入(append/insert)</li>
<li>esc退出</li>
</ul>
<ol start="3">
<li>末行模式</li>
</ol>
<ul>
<li>按冒号进入</li>
<li>执行命令后进入普通模式</li>
</ul>
<ol start="4">
<li>可视模式</li>
<li>选择模式</li>
<li>Ex模式
后面3种模式不常用, 前3个模式熟练运用即可.</li>
</ol>
<h4 id="最常用的指令集合" tabindex="-1">最常用的指令集合 <a class="header-anchor" href="#最常用的指令集合" aria-label="Permalink to &quot;最常用的指令集合&quot;">&ZeroWidthSpace;</a></h4>
<ol>
<li>[数字] + hjkl/方向键, 跳转光标,前置数字跳转指定的行</li>
<li>[数字] + dd 删除一行或指定行数, dG删到末尾, d1G删到开头, dnG删到光标在n行处; n + x/X按字符删除, x往后删, X往前删</li>
<li>[数字] + yy 复制当前行, yG/ynG与dd相似, 批量复制行, 与p/P 往前/往后粘贴搭配使用</li>
<li>u撤销, Ctrl+r重做</li>
<li>末行模式下: wq保存退出, q!放弃保存退出, ZZ命令相当于执行了:wq</li>
<li>/ + xxx, 搜索xxx字符, 也可以跟正则表达式, n下一个,N上一个, 前面可以加数字跳转多个</li>
<li>:%s/regex/replacement/gc 文本替换, %全文替换, 不加%单行替换, 加g替换所有, 不加g替换单个, c表示每次替换需要确认</li>
<li>v/V进入文本选择, 配合+y/y +p/p可以批量选择,复制粘贴(带加号复制粘贴系统剪切板不一定支持, 不带加号是寄存器复制粘贴)</li>
<li>:set nu 设置行号 :set nonu 取消行号</li>
<li>ctrl+f: 下翻一屏 ctrl+b: 上翻一屏</li>
</ol>
<h4 id="vim操作脑图-盗来的图" tabindex="-1">Vim操作脑图(盗来的图) <a class="header-anchor" href="#vim操作脑图-盗来的图" aria-label="Permalink to &quot;Vim操作脑图(盗来的图)&quot;">&ZeroWidthSpace;</a></h4>
<p><img src="//filecdn.code2life.top/vim-01.png" alt="vim"></p>
<h4 id="vim键盘" tabindex="-1">Vim键盘 <a class="header-anchor" href="#vim键盘" aria-label="Permalink to &quot;Vim键盘&quot;">&ZeroWidthSpace;</a></h4>
<p><img src="//filecdn.code2life.top/vim-02.png" alt="vim"></p>
<h4 id="结语" tabindex="-1">结语 <a class="header-anchor" href="#结语" aria-label="Permalink to &quot;结语&quot;">&ZeroWidthSpace;</a></h4>
<p>任何一个领域稍微深入一些探索都远比在远处旁观复杂的多, 对技术保持敬畏.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[C# 读取XML的三种方式]]></title>
            <link>https://code2life.top/blog/0013-csharp-xml-rw</link>
            <guid>https://code2life.top/blog/0013-csharp-xml-rw</guid>
            <pubDate>Mon, 15 Jan 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="c-读取xml的三种方式" tabindex="-1">C# 读取XML的三种方式 <a class="header-anchor" href="#c-读取xml的三种方式" aria-label="Permalink to &quot;C# 读取XML的三种方式&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="两种读写模型" tabindex="-1">两种读写模型 <a class="header-anchor" href="#两种读写模型" aria-label="Permalink to &quot;两种读写模型&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="dom模型" tabindex="-1">DOM模型 <a class="header-anchor" href="#dom模型" aria-label="Permalink to &quot;DOM模型&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>DOM的好处在于它允许编辑和更新XML文档，可以随机访问文档中的数据，可以使用XPath查询，但是，DOM的缺点在于它需要一次性的加载整个文档到内存中，对于大型的文档，这会造成资源问题</p>
</blockquote>
<h4 id="流模型" tabindex="-1">流模型 <a class="header-anchor" href="#流模型" aria-label="Permalink to &quot;流模型&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>流模型很好的解决了DOM模型的性能问题，因为它对XML文件的访问采用的是流的概念，也就是说，任何时候在内存中只有当前节点，但它也有它的不足，它是只读的，仅向前的，不能在文档中执行向后导航操作。</p>
</blockquote>
<h2 id="三种读写方式" tabindex="-1">三种读写方式 <a class="header-anchor" href="#三种读写方式" aria-label="Permalink to &quot;三种读写方式&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="方式一-内置dom库-xmldocument" tabindex="-1">方式一：内置DOM库 XmlDocument <a class="header-anchor" href="#方式一-内置dom库-xmldocument" aria-label="Permalink to &quot;方式一：内置DOM库 XmlDocument&quot;">&ZeroWidthSpace;</a></h4>
<p>MSDN传送门：<a href="https://msdn.microsoft.com/zh-cn/library/system.xml(v=vs.110).aspx"><a href="https://msdn.microsoft.com/zh-cn/library/system.xml(v=vs.110).aspx" target="_blank" rel="noreferrer">https://msdn.microsoft.com/zh-cn/library/system.xml(v=vs.110).aspx</a></a></p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//声明Document并加载xml</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlDocument</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> doc</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> XmlDocument</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlReaderSettings</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> settings</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> XmlReaderSettings</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">settings.IgnoreComments </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//忽略文档里面的注释</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlReader</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> reader</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> XmlReader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Create</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">@"..\..\Book.xml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, settings);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">doc.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Load</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(XmlReader); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//也可以不使用XMLReader,直接doc.Load(@"Path/xx.xml")或doc.LoadXML("xmlstring...")</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlNode</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> xmlDoc.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SelectSingleNode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"bookstore"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//获取名为bookstore的节点</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlNodeList</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> nodeList</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> xn.ChildNodes;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">foreach</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlNode</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> node</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> nodeList )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    XmlElement</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xe</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)node; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//转换为元素</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(xe.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetAttribute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ISBN"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ToString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//得到ISBN属性的值</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    XmlNodeList</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> subNodes</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> xe.ChildNodes;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    Console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">WriteLine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(subNodes.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).InnerText; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//第一个子节点的文本</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Close</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();      </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//使用XMLReader时注意手动关掉</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>增加删除修改节点详见 Mark：<a href="//www.cnblogs.com/a1656344531/archive/2012/11/28/2792863.html" target="_blank">//www.cnblogs.com/a1656344531/archive/2012/11/28/2792863.html</a></p>
<h4 id="方式二-流式读写xmltextreader和xmltextwriter" tabindex="-1">方式二：流式读写XmlTextReader和XmlTextWriter <a class="header-anchor" href="#方式二-流式读写xmltextreader和xmltextwriter" aria-label="Permalink to &quot;方式二：流式读写XmlTextReader和XmlTextWriter&quot;">&ZeroWidthSpace;</a></h4>
<p>API传送门 : <a href="https://msdn.microsoft.com/zh-cn/library/system.xml.xmltextreader(v=vs.110).aspx" target="_blank"><a href="https://msdn.microsoft.com/zh-cn/library/system.xml.xmltextreader(v=vs.110).aspx" target="_blank" rel="noreferrer">https://msdn.microsoft.com/zh-cn/library/system.xml.xmltextreader(v=vs.110).aspx</a></a></p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XmlTextReader</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> reader</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> XmlTextReader</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">@"..\..\Book.xml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">while</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Read</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (reader.NodeType </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> XmlNodeType.Element)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (reader.Name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "book"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)  {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetAttribute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">GetAttribute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ISBN"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (reader.Name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "price"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            reader.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ReadElementString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (reader.NodeType </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> XmlNodeType.EndElement)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><h4 id="方式三-linq-to-xml" tabindex="-1">方式三：Linq to XML <a class="header-anchor" href="#方式三-linq-to-xml" aria-label="Permalink to &quot;方式三：Linq to XML&quot;">&ZeroWidthSpace;</a></h4>
<p>强大的Linq, 万能的Linq: <a href="https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx" target="_blank"><a href="https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx" target="_blank" rel="noreferrer">https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx</a></a></p>
<div class="language-cs vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cs</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XElement</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> xe</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> XElement.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Load</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">@"..\..\Book.xml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">IEnumerable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">XElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">elements</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> from</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ele</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> xe.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Elements</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"book"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> where</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ele.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Attribute</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"ISBN"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Equals</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"XX"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ele;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">elements.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Remove</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[JavaScript函数节流和去抖]]></title>
            <link>https://code2life.top/blog/0012-debounce-throttle</link>
            <guid>https://code2life.top/blog/0012-debounce-throttle</guid>
            <pubDate>Fri, 12 Jan 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="javascript函数节流和去抖" tabindex="-1">JavaScript函数节流和去抖 <a class="header-anchor" href="#javascript函数节流和去抖" aria-label="Permalink to &quot;JavaScript函数节流和去抖&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="概念" tabindex="-1">概念 <a class="header-anchor" href="#概念" aria-label="Permalink to &quot;概念&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="函数节流-throttle" tabindex="-1">函数节流 throttle <a class="header-anchor" href="#函数节流-throttle" aria-label="Permalink to &quot;函数节流 throttle&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出.也就是会说预先<span style="color: #ff0000;">设定一个执行周期</span>,当<span style="color: #ff0000;">调用动作的时刻大于等于执行周期则执行该动作</span>,然后进入下一个新周期</p>
</blockquote>
<h4 id="函数去抖-debounce" tabindex="-1">函数去抖 debounce <a class="header-anchor" href="#函数去抖-debounce" aria-label="Permalink to &quot;函数去抖 debounce&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>滚动条不停的拖动, 只有停下的时候再去执行事件响应, 而不是每次触发onscroll都去执行它.也就是说当<span style="color: #ff0000;">调用动作n毫秒后</span>，才会执行该动作，若在<span style="color: #ff0000;">这n毫秒内又调用此动作则将重新计算执行时间</span></p>
</blockquote>
<h2 id="javascript实现" tabindex="-1">JavaScript实现 <a class="header-anchor" href="#javascript实现" aria-label="Permalink to &quot;JavaScript实现&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="民用级别实现方式" tabindex="-1">民用级别实现方式 <a class="header-anchor" href="#民用级别实现方式" aria-label="Permalink to &quot;民用级别实现方式&quot;">&ZeroWidthSpace;</a></h4>
<p>简洁明了</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> debounce</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">idle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">action</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> last;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ctx </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(last);    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果在timeout内调用, 则清除timeout重新计时</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    last </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        action.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ctx, args);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }, idle);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> throttle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">delay</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">action</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> last;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> curr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Date</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// '+' 转换为Number类型</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (curr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> last </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt; delay){  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 超过间隔执行并重新设置上次执行时间</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      action.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      last </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> curr;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br></div></div><h4 id="军用级别实现方式-underscore1-8-3版本源码" tabindex="-1">军用级别实现方式[underscore1.8.3版本源码] <a class="header-anchor" href="#军用级别实现方式-underscore1-8-3版本源码" aria-label="Permalink to &quot;军用级别实现方式[underscore1.8.3版本源码]&quot;">&ZeroWidthSpace;</a></h4>
<p>实现原理与民用级实现相同, 增加了<span style="color: #ff0000;">trailing edge</span>模式, 使用场景更多, 逻辑更加严密</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">_.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">debounce</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">immediate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeout, result;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> later</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;    </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (args) result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context, args);  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//传参数时才调用(即immediate为true的首次调用时, 只把timeout清除而不调用)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> debounced </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> restArgs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">args</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//restArgs将数组参数, 转换为不定参数的形式</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timeout) </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timeout); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//先清除timeout</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (immediate) {           </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果是leading edge[第一次调用时直接执行]</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> callNow </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">timeout;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//timeout为假值时是未调用过的状态, callNow设为true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(later, wait);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (callNow) result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, args);  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//直接调用</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {                   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//trailing edge, 第一次调用设置timeout</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">delay</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(later, wait, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, args);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  debounced.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cancel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">timeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//清除timeout待执行函数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;         </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//清除timeout句柄</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> debounced;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">_.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">throttle</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">options</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeout, context, args, result;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">options) options </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {};</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> later</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> options.leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> :</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//leading edge设置上次调用时间为1970年</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context, args);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">timeout) context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> throttled</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> now </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> _.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp; options.leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> now; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//trailing edge模式,调用时计时,remaining始终=wait</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remaining </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> wait </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (now </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> previous);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (remaining </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">lt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remaining </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt; wait) { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//remaining &#x26;gt; wait是指系统时间被修改到过去, 也会执行</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timeout) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timeout);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> now;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context, args);    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//leading edge直接调用</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">timeout) context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp; options.trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果有计时器进行中, 不执行</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(later, remaining); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//trailing edge调用时, 计算出距离下次可调用的时间间隔并设置定时</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  throttled.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cancel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timeout);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    previous </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    timeout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> throttled;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br></div></div><h4 id="骨灰级别实现方式-lodash4-14-2源码" tabindex="-1">骨灰级别实现方式[lodash4.14.2源码] <a class="header-anchor" href="#骨灰级别实现方式-lodash4-14-2源码" aria-label="Permalink to &quot;骨灰级别实现方式[lodash4.14.2源码]&quot;">&ZeroWidthSpace;</a></h4>
<p>此种实现方式的<span style="color: #ff0000;">debounce兼具throttle的功能</span>, 封装合理,较之于lodash3.10.*的代码, 更加容易阅读.</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> debounce</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">options</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastArgs,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  lastThis,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  maxWait,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  result,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  timerId,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  lastCallTime,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  lastInvokeTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  maxing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'function'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	throw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TypeError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">FUNC_ERROR_TEXT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  wait </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> toNumber</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(wait) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">isObject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(options)) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">options.leading;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	maxing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'maxWait'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> options;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	maxWait </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> nativeMax</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toNumber</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(options.maxWait) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, wait) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxWait;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'trailing'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> options </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">options.trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> trailing;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> invokeFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> args </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastArgs,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		thisArg </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastThis;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastArgs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastThis </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastInvokeTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(thisArg, args);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> leadingEdge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Reset any `maxWait` timer.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastInvokeTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Start the timer for the trailing edge.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timerExpired, wait);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Invoke the leading edge.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> invokeFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> remainingWait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeSinceLastCall </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastCallTime,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		timeSinceLastInvoke </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastInvokeTime,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> wait </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeSinceLastCall;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> nativeMin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(result, maxWait </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeSinceLastInvoke) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> shouldInvoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timeSinceLastCall </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastCallTime,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		timeSinceLastInvoke </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastInvokeTime;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Either this is the first call, activity has stopped and we're at the</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// trailing edge, the system time has gone backwards and we're treating</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// it as the trailing edge, or we've hit the `maxWait` limit.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (lastCallTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timeSinceLastCall </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> wait) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  (timeSinceLastCall </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">lt; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (maxing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp; timeSinceLastInvoke </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxWait));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> timerExpired</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">shouldInvoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time)) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> trailingEdge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Restart the timer.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timerExpired, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">remainingWait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> trailingEdge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// Only invoke if we have `lastArgs` which means `func` has been</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// debounced at least once.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">amp; lastArgs) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> invokeFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastArgs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastThis </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> cancel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">	  clearTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timerId);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastInvokeTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastArgs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastCallTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lastThis </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> flush</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> trailingEdge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> debounced</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> now</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		isInvoking </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> shouldInvoke</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(time);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastArgs </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastThis </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	lastCallTime </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> time;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (isInvoking) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">		return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> leadingEdge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(lastCallTime);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (maxing) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">		// Handle invocations in a tight loop.</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timerExpired, wait);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">		return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> invokeFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(lastCallTime);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  timerId </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(timerExpired, wait);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> result;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  debounced.cancel </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> cancel;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  debounced.flush </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> flush;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> debounced;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> throttle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">wait</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">options</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	  trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> func </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'function'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	throw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TypeError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">FUNC_ERROR_TEXT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">isObject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(options)) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'leading'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> options </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">options.leading </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> leading;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'trailing'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> options </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> !!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">options.trailing </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> trailing;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> debounce</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(func, wait, {</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	'leading'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: leading,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	'maxWait'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: wait,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">	'trailing'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: trailing</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br><span class="line-number">107</span><br><span class="line-number">108</span><br><span class="line-number">109</span><br><span class="line-number">110</span><br><span class="line-number">111</span><br><span class="line-number">112</span><br><span class="line-number">113</span><br><span class="line-number">114</span><br><span class="line-number">115</span><br><span class="line-number">116</span><br><span class="line-number">117</span><br><span class="line-number">118</span><br><span class="line-number">119</span><br><span class="line-number">120</span><br><span class="line-number">121</span><br><span class="line-number">122</span><br><span class="line-number">123</span><br><span class="line-number">124</span><br><span class="line-number">125</span><br><span class="line-number">126</span><br><span class="line-number">127</span><br><span class="line-number">128</span><br><span class="line-number">129</span><br><span class="line-number">130</span><br><span class="line-number">131</span><br><span class="line-number">132</span><br><span class="line-number">133</span><br><span class="line-number">134</span><br><span class="line-number">135</span><br><span class="line-number">136</span><br><span class="line-number">137</span><br><span class="line-number">138</span><br><span class="line-number">139</span><br></div></div><h2 id="应用" tabindex="-1">应用 <a class="header-anchor" href="#应用" aria-label="Permalink to &quot;应用&quot;">&ZeroWidthSpace;</a></h2>
<ul>
<li>
<p>在Web前端 resize, sroll, mousemove, mousedrag等事件触发时的频率很高, 如果注册的事件中有耗时的DOM操作, 或ajax等, 往往需要去抖或去抖来提升性能, 减少不必要的调用.</p>
</li>
<li>
<p>比如点击按钮时向后台发送ajax请求, 在<span style="color: #ff0000;">一定时间内重复点击不应该再次发送</span>, 这可以用<span style="color: #ff0000;">节流</span>实现</p>
</li>
<li>
<p>还有页面滚动触发的业务逻辑操作, 应当在<span style="color: #ff0000;">滚动停下时再去执行,</span> 这可以用<span style="color: #ff0000;">去抖</span>实现. 而是否延迟响应可以通过配置<span style="color: #ff0000;">leading edge</span>或<span style="color: #ff0000;">trailing edge</span>实现.</p>
</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Mybatis Generator的配置和使用]]></title>
            <link>https://code2life.top/blog/0011-mybatis-generator</link>
            <guid>https://code2life.top/blog/0011-mybatis-generator</guid>
            <pubDate>Sat, 06 Jan 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2 id="mybatis-generator的配置" tabindex="-1">Mybatis Generator的配置 <a class="header-anchor" href="#mybatis-generator的配置" aria-label="Permalink to &quot;Mybatis Generator的配置&quot;">&ZeroWidthSpace;</a></h2>
<p>Mybatis Generator是一个很强大的代码生成工具, 能够根据配置生成数据库对应的ORM对象, Mybatis映射SQL语句, 数据接口等
配置方式见下面的xml代码</p>
<div class="language-xml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;?</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">xml</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1.0"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> encoding</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"UTF-8"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">?></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;!</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">DOCTYPE</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> generatorConfiguration</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    "//mybatis.org/dtd/mybatis-generator-config_1_0.dtd"></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">generatorConfiguration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    &#x3C;!--导入属性配置 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">properties</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> resource</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"jdbc.properties"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    &#x3C;!--指定特定数据库的jdbc驱动jar包的classpath --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">classPathEntry</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> location</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${driverLocation}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">context</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"default"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetRuntime</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"MyBatis3"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!-- optional，旨在创建class时，对注释进行控制 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">commentGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"suppressDate"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">&#x3C;!-- 是否取消日期 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"suppressAllComments"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">&#x3C;!-- 是否取消注释 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">commentGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!--jdbc的数据库连接 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">jdbcConnection</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> driverClass</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${driver}"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> connectionURL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${url}"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> userId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${username}"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">                        password</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"${password}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">jdbcConnection</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!-- 非必需，类型处理器，在数据库类型和java类型之间的转换控制 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaTypeResolver</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"forceBigDecimals"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaTypeResolver</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!-- 下面是重点! javaModelGenerator配置生成的ORM对象, targetProject填写目标项目的相对路径 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaModelGenerator</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetPackage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"model"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetProject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"src/main/java"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            &#x3C;!-- 是否允许子包，即targetPackage.schemaName.tableName --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"enableSubPackages"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            &#x3C;!-- 是否对类CHAR类型的列的数据进行trim操作 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"trimStrings"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaModelGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">sqlMapGenerator</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetPackage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"mapping"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetProject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"src/main/java"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"enableSubPackages"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">sqlMapGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaClientGenerator</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetPackage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dao"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> targetProject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"src/main/java"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"XMLMAPPER"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">property</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"enableSubPackages"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">javaClientGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        &#x3C;!-- 具体每张表的ORM配置, 选择性的配置需要的数据接口 --></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">table</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> tableName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"user"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> domainObjectName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"User"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> enableInsert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"true"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  enableCountByExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">               enableUpdateByExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> enableDeleteByExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> enableSelectByExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> selectByExampleQueryId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"false"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">table</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">context</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">generatorConfiguration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br></div></div><p>详细配置说明见<a target="_blank" href="//www.cnblogs.com/liuconglin/p/5641146.html">这里</a></p>
<h2 id="maven-idea下使用mybatisgenerator" tabindex="-1">Maven + Idea下使用MybatisGenerator <a class="header-anchor" href="#maven-idea下使用mybatisgenerator" aria-label="Permalink to &quot;Maven + Idea下使用MybatisGenerator&quot;">&ZeroWidthSpace;</a></h2>
<p>IntelliJ IDEA下没有MybatisGenerator的插件, 可以使用<span style="color:red">Maven插件</span>解决这个问题
首先, 在pom文件的build节点下添加如下插件</p>
<div class="language-xml vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">xml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">plugins</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">plugin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">groupId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>org.mybatis.generator&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">groupId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">artifactId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>mybatis-generator-maven-plugin&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">artifactId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>1.3.2&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">configuration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">				&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">verbose</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>true&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">verbose</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">				&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">overwrite</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>true&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">overwrite</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">			&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">configuration</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">		&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">plugin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">plugins</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>其次, 添加Maven的 Run configuration, 启动命令如下
然后在classpath中添加generatorConfig.xml文件, 配置内容见示例<br>
<img src="//filecdn.code2life.top/idea-run-config.png" alt="run"><br>
最后运行配置的Run Configuration即可<br>
<strong>注: iBatis/Mybatis mini plugin这个插件能够自动提示sql,非常爽</strong></p>
<h2 id="eclipse-插件-使用mybatisgenerator" tabindex="-1">Eclipse + 插件 使用MybatisGenerator <a class="header-anchor" href="#eclipse-插件-使用mybatisgenerator" aria-label="Permalink to &quot;Eclipse + 插件 使用MybatisGenerator&quot;">&ZeroWidthSpace;</a></h2>
<p>Eclipse中下载这个插件, 能够直接在new file中找到Mybatis Generator Configuration<br>
<img src="//filecdn.code2life.top/mybatis-plugin.png" alt="plugin"><br>
配置好后, 右击generatorConfig.xml, 选择下面的Run as启动即可<br>
<img src="//filecdn.code2life.top/eclipse-mybatis-generator.png" alt="run"><br>
<strong>注: 插件运行的classpath与Maven下的不同, 这里可能还要添加一层项目文件夹目录作为相对路径</strong></p>
<h2 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">&ZeroWidthSpace;</a></h2>
<p>经过数个项目的实践, MybatisGenerator的确是一个能搞大幅度提高开发效率的工具, 但要注意避免过度依赖, 如果开启过多<strong>Example</strong>生成, 会造成相关sqlMapper文件很大, 难以维护, 并且自动生成的按字段进行通用的增删改查接口性能方面肯定差于直接按照业务编写sqlMapper. 除了基本的模型类和sqlMapper外, 哪些额外的接口需要自动生成需要视业务而定, 这样才能在开发效率, 可维护性, 安全, 性能等方面得到权衡.</p>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[自己动手实现平滑滚动条]]></title>
            <link>https://code2life.top/blog/0010-js-scroll-easing</link>
            <guid>https://code2life.top/blog/0010-js-scroll-easing</guid>
            <pubDate>Wed, 20 Dec 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="自己动手实现平滑滚动条" tabindex="-1">自己动手实现平滑滚动条 <a class="header-anchor" href="#自己动手实现平滑滚动条" aria-label="Permalink to &quot;自己动手实现平滑滚动条&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="前期调研" tabindex="-1">前期调研 <a class="header-anchor" href="#前期调研" aria-label="Permalink to &quot;前期调研&quot;">&ZeroWidthSpace;</a></h2>
<p>当前浏览器的标准也越来越明确, 用户体验越来越重要. 相比于FireFox, Chrome没有自带平滑滚动, 鼠标滚轮滚动时直接滚动到指定位置, 没有实现缓动, 这应该是出于性能的考虑. 那么, 如何手工去实现平滑滚动, 或者说是带动画的滚动呢?
<strong>首先, Mark几个不错的开源项目, 打开传送门</strong></p>
<ul>
<li>No1. <a href="https://github.com/inuyaksa/jquery.nicescroll" target="_blank">nicescroll</a> : 平滑滚动的jQuery插件, 兼容性非常不错</li>
<li>No2. <a href="https://github.com/alvarotrigo/fullPage.js" target="_blank">fullPage</a> : 整页滚动jQuery插件, 适合极简风格的介绍型页面</li>
<li>No3. <a href="https://github.com/flesler/jquery.scrollTo" target="_blank">scrollTo</a> : 平滑滚动的jQuery插件+1, 非常常用且轻量</li>
</ul>
<p>其实, 手动实现一个这样的效果并不困难, 只要做以下两件事即可</p>
<ol>
<li>截获mousewheel事件,<span style="color: #ff0000;"> 阻止浏览器默认行为</span></li>
<li>按照事件参数<span style="color: #ff0000;">处理滚轮事件</span>, 并通过动画的形式实现</li>
</ol>
<h2 id="踩坑和踩坑" tabindex="-1">踩坑和踩坑 <a class="header-anchor" href="#踩坑和踩坑" aria-label="Permalink to &quot;踩坑和踩坑&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="浏览器兼容性" tabindex="-1">浏览器兼容性 <a class="header-anchor" href="#浏览器兼容性" aria-label="Permalink to &quot;浏览器兼容性&quot;">&ZeroWidthSpace;</a></h4>
<p>不同浏览器对滚轮事件的绑定都是不一样的, 比如</p>
<ul>
<li>IE下是这样的:</li>
</ul>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(document.attachEvent){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">attachEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'onmousewheel'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,smoothBar);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><ul>
<li>FF下是这样的(FF自带了平滑滚动, 实际情况下无需绑定FF的滚轮事件)</li>
</ul>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(document.addEventListener){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'DOMMouseScroll'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,smoothBar,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><ul>
<li>Chrome/Safari下是这样的</li>
</ul>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">window.onmousewheel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">document.onmousewheel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">smoothBar;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h4 id="还是浏览器的兼容性问题" tabindex="-1">还是浏览器的兼容性问题 <a class="header-anchor" href="#还是浏览器的兼容性问题" aria-label="Permalink to &quot;还是浏览器的兼容性问题&quot;">&ZeroWidthSpace;</a></h4>
<p>稍低版本的IE浏览器事件<span style="color: #ff0000;">没有target属性, 也没有preventDefault函数</span><br>
这里是一个简单的fix方法</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">eventToFix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (eventToFix </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix.target) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  eventToFix</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">eventToFix</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> window.event;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  eventToFix.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">preventDefault</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.returnValue </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; };</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h4 id="仍然是浏览器的兼容性" tabindex="-1">仍然是浏览器的兼容性 <a class="header-anchor" href="#仍然是浏览器的兼容性" aria-label="Permalink to &quot;仍然是浏览器的兼容性&quot;">&ZeroWidthSpace;</a></h4>
<p>设置或获取当前滚动位置在不同浏览器也是不同的, 下面这句话能够兼容的获取滚动位置</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scrollTop </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> document.documentElement.scrollTop </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                window.pageYOffset </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                document.body.scrollTop;</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>其中, document.pageYOffset是Safari专用的.<br>
其他的坑比如IE9以下还没有requestAnimationFrame函数等等, 浏览器兼容性是个超大的坑, 在IE下没能正常跑出来, 最后其实做的是一个Chrome下的原生JS平滑滚动, <span style="color: #ff0000;">不支持IE和FF</span>.</p>
<h4 id="如何判断元素是否可滚动" tabindex="-1">如何判断元素是否可滚动 <a class="header-anchor" href="#如何判断元素是否可滚动" aria-label="Permalink to &quot;如何判断元素是否可滚动&quot;">&ZeroWidthSpace;</a></h4>
<p>这是关键性的问题, 当截流了所有的mousewheel事件后, 一个页面可能有很多scrollbar, 如何根据截取的事件判断应该让哪个元素滚起来呢?<br>
大概逻辑是这样的:</p>
<ul>
<li>如果<span style="color: #ff0000;">event.target是body元素</span>, 直接滚body</li>
<li>如果event.target是其他元素, 判断这个元素能不能滚, 如果不能, 判断父节点能不能滚<span style="color: #ff0000;">直到找到滚的起来的或body元素</span>
<strong>参考了一些资料, 发现有两种判断方式:</strong></li>
</ul>
<ol>
<li>只要element.scrollHeight &gt; element.clientHeight, 说明是个能滚的元素</li>
<li>见下图
<img src="//filecdn.code2life.top/isScrollable.png" alt="stackoverflow"></li>
</ol>
<p>这两种都是不准确的, scrollHeight &gt; clientHeight不一定是有滚动条, 可能有其他原因, 具体原因尚待验证,<br>
其次overflow:visible的元素也可能是有滚动条的</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 原生JS代码 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> isScrollable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">element</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> window.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getComputedStyle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element)[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'overflow-y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//需要根据计算后的style判断而不能根据元素的css属性</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'scroll'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'auto'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> node.scrollHeight </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> node.clientHeight;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* jQuery写法 */</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">height</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> element.clientHeight </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">( </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  $</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">css</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'overflow-Y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'scroll'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  $</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">css</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'overflow-Y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'auto'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h2 id="实践与结果" tabindex="-1">实践与结果 <a class="header-anchor" href="#实践与结果" aria-label="Permalink to &quot;实践与结果&quot;">&ZeroWidthSpace;</a></h2>
<p>实现思路已经很明确, 截取滚轮事件和判断元素是否可滚动已经理解, 只欠把对应的元素用动画滚起来了.
前端实现动画有几种方法:</p>
<ol>
<li>css3 transition动画或animation+keyframes实现, css并<span style="color: #ff0000;">不支持</span>scrollTop属性的动画, 支持动画的css属性有<a href="//oli.jp/2010/css-animatable-properties/">这些</a></li>
<li>jQuery插件实现, 不用自己造轮子</li>
<li>使用<span style="color: #ff0000;">requestAnimationFrame</span>函数自己写一个高性能的平滑滚动, 享受造轮子的乐趣</li>
</ol>
<p><strong>代码如下~</strong></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">window.onmousewheel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">document.onmousewheel</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">smoothBar;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> smoothBar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){ </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //fix事件属性的差异. 曾经美好的浏览器兼容的愿望↓</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> event </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">eventToFix</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (eventToFix </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix.target) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      eventToFix</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">eventToFix</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> window.event;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      eventToFix.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">preventDefault</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.returnValue </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; };</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> eventToFix;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })(e);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  event.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">preventDefault</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> counter </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//记录当前帧数</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxCount </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 90</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//90个帧的动画</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> progress </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//当前百分比</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scrollCache </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {}; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//元素是否可滚动缓存</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> proCache </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(window.WeakMap) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //有weakmap使用它当缓存更合适</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    proCache </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> WeakMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> delta </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">event.wheelDelta; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//滚动量</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> requestAnimationFrame </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "undefined"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> requestAnimationFrame </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> setTimeout; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//为了兼容~</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> generateDelta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    counter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /* 用sin函数做了个简单的ease,略带惯性效果 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tempProgress </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">sin</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(counter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">maxCount </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Math.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">PI</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tempResult </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> delta </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (tempProgress </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> progress);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    progress </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tempProgress;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /* 计算出的当前帧应滚数值 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tempResult;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> srollAbleEle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">node</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> window.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getComputedStyle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node)[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'overflow-y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'scroll'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> overflowY </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'auto'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> node.scrollHeight </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> node.clientHeight;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  /* 判断元素是否可滚动 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> isScrollable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">node</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(proCache) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">proCache.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">has</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node)) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        proCache.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">srollAbleEle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> proCache.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scrollCache[node.innerHTML] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'undefined'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        scrollCache[node.innerHTML] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> srollAbleEle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(node);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scrollCache[node.innerHTML]; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//缓存后就不用每次计算了(在元素不会动态改变属性的前提下)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getScrollElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">element</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">isScrollable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> element.tagName </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "BODY"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> element;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //如果不能滚动, 找到第一个能滚的祖先</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getScrollElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(element.parentNode);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //如果页面只有一个可竖向滚动的元素, 直接指定比实时计算快很多</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //var scrollEle = $("#scrollDiv")[0];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /* 在浏览器渲染的下一帧执行滚动 */</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    requestAnimationFrame</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> deltaVal </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> generateDelta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">event.target) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> scrollEle </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getScrollElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(event.target);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        scrollEle.scrollTop </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> deltaVal;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        document.body.scrollTop </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> deltaVal;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(counter </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> maxCount) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  })();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[Linux入门] Systemd与开机自启动]]></title>
            <link>https://code2life.top/blog/0009-linux-systemd-startup</link>
            <guid>https://code2life.top/blog/0009-linux-systemd-startup</guid>
            <pubDate>Sat, 16 Dec 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="linux入门-systemd与开机自启动" tabindex="-1">[Linux入门] Systemd与开机自启动 <a class="header-anchor" href="#linux入门-systemd与开机自启动" aria-label="Permalink to &quot;[Linux入门] Systemd与开机自启动&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="systemd简介" tabindex="-1">systemd简介 <a class="header-anchor" href="#systemd简介" aria-label="Permalink to &quot;systemd简介&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>systemd即为system daemon,是linux下的一种init软件, 目标是提供更优秀的框架以表示系统服务间的依赖关系, 并依此实现系统初始化时服务的并行启动, 同时达到降低Shell的系统开销的效果, 最终代替现在常用的System V与BSD风格init程序. 由于systemd使用了cgroup与fanotify等组件以实现其特性, 所以只适用于Linux.</p>
</blockquote>
<p>systemd是一组用于管理守护进程的Linux工具, 它取代了initd, 成为系统第一个启动的进程, 其他进程都是它的子进程. 主流的发行版Debian 8, CentOS 7, 都已经使用了systemd.然鹅, 由于其带来的巨大变化是颠覆性的, 而且其主要命令systemctl长度竟然有9个字母, 在开源社区一直饱受争议. systemd统一了Linux中多样化的自启动方式, 用更加先进的架构和实现管理进程启动(一定程度上提高了启动速度), 将系统启动相关的守护进程几乎全部纳入控制范围, 这些特点都有正反两面, 不能一概而论.</p>
<h4 id="systemd的特性" tabindex="-1">systemd的特性 <a class="header-anchor" href="#systemd的特性" aria-label="Permalink to &quot;systemd的特性&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>支持并行化任务</li>
<li>同时采用socket式与D-Bus总线式激活服务</li>
<li>按需启动守护进程（daemon）</li>
<li>利用 Linux 的 cgroups 监视进程</li>
<li>支持快照和系统恢复</li>
<li>维护挂载点和自动挂载点</li>
<li>各服务间基于依赖关系进行精密控制</li>
</ul>
<h2 id="systemd出现之前的启动流程" tabindex="-1">systemd出现之前的启动流程 <a class="header-anchor" href="#systemd出现之前的启动流程" aria-label="Permalink to &quot;systemd出现之前的启动流程&quot;">&ZeroWidthSpace;</a></h2>
<p>在systemd出现之前Linux发行版进程自启动主要有两种风格, System V与BSD(<a href="//blog.csdn.net/qq_29344757/article/details/78657874" target="_blank" rel="noreferrer">科普传送门</a>), 下面以我们经常使用的红帽系CentOs和Debian系Ubuntu为例, 归纳不同版本自启动进程的实现方式.</p>
<h4 id="sysvinit启动流程与开机自启服务的实现" tabindex="-1">SysVinit启动流程与开机自启服务的实现 <a class="header-anchor" href="#sysvinit启动流程与开机自启服务的实现" aria-label="Permalink to &quot;SysVinit启动流程与开机自启服务的实现&quot;">&ZeroWidthSpace;</a></h4>
<p>System V启动的完整流程在这里: <a href="https://www.cnblogs.com/sysk/p/4778976.html" target="_blank" rel="noreferrer">传送门</a></p>
<p>这里涉及到运行级别的概念, System V风格的linux有7种运行级别:</p>
<ul>
<li>0：系统停机状态，系统默认运行级别不能设为0，否则不能正常启动运行级别</li>
<li>1：单用户工作状态，root权限，用于系统维护，禁止远程登陆运行级别</li>
<li>2：多用户状态(没有NFS)运行级别</li>
<li>3：完全的多用户状态(有NFS)，登陆后进入控制台命令行模式运行级别</li>
<li>4：系统未使用，保留运行级别</li>
<li>5：X11控制台，登陆后进入图形GUI模式运行级别</li>
<li>6：系统正常关闭并重启，默认运行级别不能设为6，否则不能正常启动</li>
</ul>
<p>在CentOS 7之前, /etc/inittab文件可以配置运行级别, 但从CentOS 7开始, 打开此文件可以看到这个文件已经失效了, 提示需要从systemd配置中修改运行级别. 当然telinit命令也可以用于修改默认的运行级别, 下图是CentOS 7中inittab文件的内容
<img src="//filecdn.code2life.top/inittab.jpg" alt="inittab"></p>
<p>在/etc/rc.d下有7个名为rcN.d的目录，对应系统的7个运行级别, 系统会根据指定的运行级别进入对应的rcN.d目录, 并按照文件名顺序检索目录下的链接文件</p>
<ul>
<li>对于以K开头的文件，系统将终止对应的服务</li>
<li>对于以S开头的文件，系统将启动对应的服务</li>
</ul>
<p>其中rc.sysinit是系统初始化脚本, rc.local是给用户自定义启动时需要执行的文件, rc.local是所有级别的脚本运行完之后才会运行的, 也是SysV启动的最后一步.在rc.local中添加自定义的命令或运行指定脚本, 是实现自启动进程或服务的最简单的方式, 但由于难以维护管理, 并且没有守护进程的优势, 对于错误和异常没有任何处理流程, 一般不建议使用</p>
<h4 id="upstart启动流程" tabindex="-1">Upstart启动流程 <a class="header-anchor" href="#upstart启动流程" aria-label="Permalink to &quot;Upstart启动流程&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>Upstart是一个基于事件的/sbin/init守护进程的替代品, 它在启动过程中处理启动任务和服务，在关闭期间停止它们并在系统运行时监督它们. 它最初是为Ubuntu 发行版开发的，但它的目的是适合在所有Linux发行版中部署, 作为历史悠久的System-V init的替代品, 它具有以下特点</p>
</blockquote>
<ul>
<li>任务和服务按事件启动和停止</li>
<li>事件是在任务和服务启动和停止时生成的</li>
<li>事件可以从系统上的任何其他进程接收</li>
<li>如果服务意外死亡，服务可能会重新生成</li>
<li>守护进程的监督和重建，与父进程分离</li>
<li>通过D-Bus与init守护进程通信</li>
<li>用户服务，用户可以自行启动和停止</li>
</ul>
<p>Upstart是兼容SysV启动方式的, 但由于采用的更先进的事件模型, 启动速度更快. 大部分较新的发行版如Ubuntu, CentOS, Fedora在转型systemd之前, 都是使用Upstart方式启动的.</p>
<h4 id="centos-7之前实现开机自启" tabindex="-1">CentOS 7之前实现开机自启 <a class="header-anchor" href="#centos-7之前实现开机自启" aria-label="Permalink to &quot;CentOS 7之前实现开机自启&quot;">&ZeroWidthSpace;</a></h4>
<p>使用 chkconfig + service 实现自启动脚本和服务</p>
<blockquote>
<p>chkconfig命令主要用来更新（启动或停止）和查询系统服务的运行级信息。谨记chkconfig不是立即自动禁止或激活一个服务，它只是简单的改变了符号连接</p>
</blockquote>
<p>首先在/etc/init.d目录下编写一个服务脚本, 其中前两行是必须的, 第一行表示运行程序, 第二行表示运行级, 启动优先级, 停止优先级, 而description是服务的描述, 起到注释作用</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">#!/bin/bash</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># chkconfig: 2345 10 90 </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># description: http .... \</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># http service demo</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "HTTP is enabled now"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">stop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "HTTP is disable now"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">case</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">$1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> in</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">start</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  start</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ;;</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">stop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  stop</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ;;</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">restart</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  stop</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  start</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ;;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*)</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "USAGE </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">$0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> {start|stop|restart}"</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  exit</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">esac</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>服务脚本编写好之后, 可以使用chkconfig命令改变rcN.d中的K/S入口文件软连接, 具体用法如下</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 显示所有运行级系统服务的运行状态信息（on或off）</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如果指定了name，那么只显示指定的服务在不同运行级的状态</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">chkconfig</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --list</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [name]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 增加一项新的服务</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># chkconfig确保每个运行级有一项启动(S)或者杀死(K)入口</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 如有缺少，则会从缺省的init脚本自动建立</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">chkconfig</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 删除服务，并把相关符号连接从/etc/rc[0-6].d删除</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">chkconfig</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --del</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 设置某一服务在指定的运行级是被启动，停止还是重置</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">chkconfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [--level </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">levels]</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># eg: </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># chkconfig --level 35 http on</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># chkconfig mysqld off</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>chkconfig配置好, 开机将自动运行自定义的服务, 同时service命令可以手工控制服务的运行, 查看运行状态等</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">service</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> start</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">service</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> restart</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">service</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stop</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">service</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> network</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> status</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h4 id="ubuntu-16-04之前实现开机自启" tabindex="-1">Ubuntu 16.04之前实现开机自启 <a class="header-anchor" href="#ubuntu-16-04之前实现开机自启" aria-label="Permalink to &quot;Ubuntu 16.04之前实现开机自启&quot;">&ZeroWidthSpace;</a></h4>
<p>使用 update-rc.d + service 实现自启动脚本和服务, update-rc.d命令与红帽系中的chkconfig命令作用几乎一样, 能够修改rcN.d中的软连接, 从而实现自启动服务</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 95代表启动的优先级</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update-rc.d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> defaults</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 95</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># Ubuntu中也可以使用service命令</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">service</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> restart</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 移除一个自启动服务</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">update-rc.d</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> http</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> remove</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h2 id="systemd时代的自启动服务实现-centos-7-ubuntu-16-04-debian-8" tabindex="-1">Systemd时代的自启动服务实现(CentOS 7/Ubuntu 16.04/Debian 8) <a class="header-anchor" href="#systemd时代的自启动服务实现-centos-7-ubuntu-16-04-debian-8" aria-label="Permalink to &quot;Systemd时代的自启动服务实现(CentOS 7/Ubuntu 16.04/Debian 8)&quot;">&ZeroWidthSpace;</a></h2>
<p>Ubuntu 从15.04开始, 逐步使用 systemd 替代了upstart, systemd包括一系列的工具和管理单元(Unit),以CentOS 7为例, 默认的Unit存放位置在/usr/lib/systemd/system,/usr/lib/systemd/user目录中, 以及/etc/systemd/system中. 如果需要自定义一个开机自启的服务, 一般在/etc/systemd/system中建立一个serivce文件, 使用systemctl加载即可, 一个典型的service文件写法如下</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[Unit]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Description</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">nginx</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Documentation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">//nginx.org/en/docs/</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">After</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">network.target</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  remote-fs.target</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> nss-lookup.target</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[Service]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Type</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">forking</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">PIDFile</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/run/nginx.pid</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ExecStartPre</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/usr/sbin/nginx</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> -t</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/nginx/nginx.conf</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ExecStart</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/usr/sbin/nginx</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> -c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /etc/nginx/nginx.conf</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ExecReload</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/bin/kill</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> HUP</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $MAINPID</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">ExecStop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/kill</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> -s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> QUIT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> $MAINPID</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">PrivateTmp</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">true</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[Install]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">WantedBy</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">multi-user.target</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><p>具体每个区块的含义和完整解释这里有详细的描述: <a href="//www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html" target="_blank" rel="noreferrer">Mark</a><br>
在编写完成Unit文件后, 再来两行就可以了现在启动守护进程了</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># enable 相当于在对应的target.wants文件夹下建立软连接</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> enable</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxxservice</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> disable</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxxservice</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> daemon-reload</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxxservice</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h2 id="systemd其他用途" tabindex="-1">systemd其他用途 <a class="header-anchor" href="#systemd其他用途" aria-label="Permalink to &quot;systemd其他用途&quot;">&ZeroWidthSpace;</a></h2>
<p>Systemd可以做的事情远不止实现开机自启服务, systemd提供了一系列强(fu)大(za)的工具链, 最常用的比如journalctl用于查看各种日志信息</p>
<blockquote>
<p>journalctl -xe</p>
</blockquote>
<ul>
<li>louginctl查看当前登录的用户</li>
<li>timedatectl查看当前时区和时间日期设置</li>
<li>hostnamectl命令用于查看当前主机的信息</li>
<li>systemd-analyze用于查看启动耗时</li>
<li>等等</li>
</ul>
<p>最主要的systemctl命令, 玩法也有很多, 常用的比如</p>
<div class="language-bash vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> enable</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxxservice</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> daemon-reload</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> restart</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> xxxservice</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reboot</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">systemctl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> poweroff</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>完整的参数清单多的可怕, 呈上<strong>终极Shell zsh</strong>自动补全的截图
<img src="//filecdn.code2life.top/systemctl-args.jpg" alt="args"><br>
<img src="//filecdn.code2life.top/systemctl-params.jpg" alt="params"></p>
<p>查看日志的命令参数也有这些
<img src="//filecdn.code2life.top/journalctl-params.jpg" alt="params"></p>
<p>总之, systemd作为一种更先进的技术, 虽有争议, 但既然主流的发行版以及采用了这种方式, 我们应该以开放的心态, 去学习和实践, 今夕对比, 互相借鉴其中好的地方.</p>
<blockquote>
<p>Keep It Simple and Short</p>
</blockquote>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Lodash常用函数汇总]]></title>
            <link>https://code2life.top/blog/0008-lodash-doc</link>
            <guid>https://code2life.top/blog/0008-lodash-doc</guid>
            <pubDate>Wed, 13 Dec 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="lodash常用函数汇总" tabindex="-1">Lodash常用函数汇总 <a class="header-anchor" href="#lodash常用函数汇总" aria-label="Permalink to &quot;Lodash常用函数汇总&quot;">&ZeroWidthSpace;</a></h1>
<blockquote>
<p>lodash是npm上最热门的一个模块, 提供了非常多的实用函数, nodejs服务端函数式编程必备, 由于文档非常多, 这里列举了大部分常用函数的简略信息, 供查询温习使用, 文档正门在这里: <a href="https://lodash.com/docs/" target="_blank" rel="noreferrer">lodash文档</a></p>
</blockquote>
<blockquote>
<p>格式 : [函数名 - 简述 - 参数 - 返回值 - 备注信息]</p>
</blockquote>
<h4>数组扩展函数</h4>
- chrunk - 分割数组 - (数组, 块长度) - 新数组
- compact - 过滤假值元素 - 数组 - 新数组
- difference - 差集 - (src数组,...减去的数组) - 新数组
- drop - shift多个元素 - (数组, 去除数量) - 新数组
- dropRight - pop多个元素 - (数组, 去除数量) - 新数组
- dropWhile - shift直到不满足while条件 - (数组, [函数,对象|字符串]) - 新数组
- dropRightWhile - pop直到不满足while条件 - (数组, [函数|对象|字符串]) - 新数组
- fill - 填充元素 - (数组,填充值,[起始,结束]) - 数组引用 - 有副作用
- findIndex - 找索引 - (数组, [函数|对象|字符串]) - 索引
- findLastIndex - 找索引 - (数组, [函数|对象|字符串]) - 索引
- first - array[0] - 数组 - 元素
- flatten - 展开内部数组 - (数组,[是否递归]) - 新数组
- flattenDeep - 递归展开 - 数组 - 新数组
- indexOf - 找索引 - (数组, 数组元素, [何处开始|是否二分]) - 索引
- initial - 除末尾元素 - 数组 - 新数组
- intersection - 交集 - ...数组 - 新数组
- last - array[length-1] - 数组 - 末尾元素
- lastIndexOf - 找索引 - (数组, 数组元素, [何处开始|是否二分]) - 索引
- object/zipObject - 转对象 - (嵌套数组|Key数组+Value数组) - 对象
- pull - 推出元素 - (数组, ...数组元素) - 移除元素数组 - 有副作用
- pullAt - 推出索引处元素 - (数组, ...索引) - 移除元素数组 - 有副作用
- remove - 按条件移除元素 - (数组, [函数|对象|字符串]) - 移除元素数组 - 有副作用
- rest/tail - 除开头元素 - 数组 - 新数组
- slice - 截取数组 - (数组, [起始=0, 结束=length]) - 新数组
- sortedIndex - 寻找插入排序的插入位置 - (数组, 待排序元素, [函数|对象|字符串])
- sortedLastIndex - 自右寻找插入排序的插入位置 - (数组, 待排序元素, [函数|对象|字符串])
- take - 自左取n个元素 - (数组, [待取数目=1]) - 新数组
- takeRight - 自右取n个元素 - (数组, [待取数目=1]) - 新数组
- takeWhile - 按条件取元素 - (数组, [函数|对象|字符串]) - 新数组
- takeRightWhile - 按条件自右取元素 - (数组, [函数|对象|字符串]) - 新数组
- union - 并集 - (...数组) - 新数组
- uniq/unique - 去重 - (数组, [是否排序], [迭代器]) - 新数组
- unzip & zip & unzipWith 数组合并拆分 - //lodashjs.com/docs/#_unziparray
- without - 除去元素 - (数组, 元素) - 新数组
- xor - 并集减交集,交之补,补之并 - (...数组) - 新数组
<h4>集合扩展函数</h4>
- all/every - 集合每一个元素是否满足条件 - (集合, [函数|对象|字符串]) - 布尔 | 集合: 对象,数组或字符串
- any/some - 集合是否含有满足条件的元素 - (集合, [函数|对象|字符串]) - 布尔
- at - 按索引或键选择集合元素 - (集合, [索引|属性]) - 值数组
- map/collect - 映射 - (集合, [函数|属性]) - 映射后数组
- reduce/foldl/inject - 归并 - (集合, 归并函数) - 归并值 - 归并函数参数: 累加对象, 当前value, 当前key
- reduceRight/foldr - 从右归并 - (集合, 归并函数) - 归并值
- contains/includes - 是否包含值 - (集合, 目标元素或集合, [起始位置]) - 布尔
- countBy - count(*) group by - (集合, [函数|对象|字符串]) - 对象 | { 返回值 : 数目 }
- find/detect - 寻找元素 - (集合, [函数|对象|字符串]) - 集合中一个元素或undefined
- findLast - 从右寻找元素 - (集合, [函数|对象|字符串]) - 集合中一个元素或undefined
- each/forEach - 扩展Array.prototype.forEach - (集合, 迭代器函数) - 集合本身
- eachRight/forEachRight - 同上,从右往左迭代 - (集合, 迭代器函数) - 集合本身
- filter - 过滤元素,takeWhile - (数组集合, 过滤函数) - 符合过滤条件的集合
- reject - 过滤元素,dropWhile - (数组集合, 过滤函数) - 不符合条件的集合
- where - 过滤元素 - (数组集合, match对象) - 符合条件的集合
- invoke - 对集合每个元素调用函数 - (集合, 函数名|函数, ...参数) - 结果集
- indexBy - 创建索引 - (集合, 索引key) - 索引对象
- partition - 分割集合 - (集合, 分割函数) - 结果集合
- groupBy - 分组 - (集合, 分组函数) - 结果对象, key为分组函数返回值, value为组内集合
- pluck - 摘出集合中对象的value集 - (集合, 元素的key) - 元素value集合
- sample - 取样本 - (集合, 样本容量) - 样本集合
- shuffle - 打乱集合 - (集合) - 打乱后的集合 - Fisher-Yates洗牌算法
- size - 集合大小 - (集合) - 集合的容量
- sortBy - 单键排序 - (集合, [函数|对象|字符串]) - 排序结果集
- sortAll - 多键排序 - (集合, [[函数|对象|字符串]]) - 排序结果集 - 排序迭代器为数组
- sortByOrder - 多键排序 - (集合, [[函数|对象|字符串]], ['asc'|'desc']) - 排序结果集
<h4>对象</h4>
- assign - 赋值到目标对象 - (对象, ...源对象) - 目标对象
- at - 取对象属性的值 - (对象, [字符串/字符串数组]) - 取到的值数组
- default - 不覆盖赋值 - (对象, ...源对象) - 目标对象 - 如果目标对象已存在的属性不会覆盖赋值
- get - 取对象的属性值 - (对象, 字符串, [默认值]) - 取到的值
- set - 设置对象属性值 - (对象, 路径字符串, 值) - 设置的值
- has - 是否存在属性 - (对象, 字符串) - 布尔
- invert - 键值反转 - (对象) - 反转后对象
- invertBy -指定key生成条件的键值反转 - (对象, [函数]) - 反转后对象
- merge - 递归合并对象 - (对象, ...其他对象) - 合并后的对象 - 同一个键合并为数组, 数组内多个对象合并同一个对象
- omit - 排除键值 - (对象, ...路径字符串) - 排除键值后的对象
- pick - 选择键值- (对象, ...路径字符串) - 选择特定键值后的对象
- omitBy - 根据条件排除键值 - (对象, 过滤函数) - 排除后的对象
- pickBy - 根据条件选择键值 - (对象, 过滤函数) - 选择后的对象
- unset - 删除属性 - (对象, 路径字符串) - 删除属性后的对象
- transform - 对象的reduce - (对象, reduce函数) - 转换后的对象
- update - 更新值 - (对象, 路径字符串, 映射函数) - 更新后的对象
<h4>工具类</h4>
- now - 当前时间戳 - () - 1970到当前毫秒数
- add - 累加 - (被加数, 加数) - 和
- sum - 集合累加 - (集合, [函数|对象|字符串]) - 总和
- ceil - 向上截取小数 - (带截取数, 精度=0) - 截取值 - 精度默认0取整,负数则向整数位取约数
- floor - 向下截取小数 - (带截取数, 精度=0) - 截取值
- round - 四舍五入 - (带结区属, 精度=0) - 截取值
- min - 取最小值 - (集合, [函数|对象|字符串]) - 最小值
- max - 取最大值 - (集合, [函数|对象|字符串]) - 最大值
- inRange - 是否在范围内 - (数字, [>的数], <的数) - 布尔
- random - 取随机值 - (最小数, 最大数, 是否允许小数) - 随机值
- clone - 复制 - (复制值, [是否深复制=false], [值包装器]) - 复制后的值
- cloneDeep - 深拷贝 - (复制值, [值包装器]) - 复制后的值
- gt/gte/lt/lte - 值比较 - (value1, value2) - 布尔 - 对于字符串调用的普通compare而非local的
- is+Array/Boolean/Date/Empty/Error/Object/Number/Function/NaN/Null/Native/PlanObject/String/RegExp/TypedArray/Undefined/Element/Arguments/Finite - 判断是否满足特定条件 - (值) - 布尔
- isMatch/isEqual - 判断是否匹配或相等 - (源, 目标, [定制化比较器]) - 布尔 - isMatch用于判断对象是否Match某些特定键值对
- to+Array/PlainObject - 转换为标准数组/展开原型链转换为Pojo对象 - (对象) - 转换后的对象
<h4>函数扩展</h4>
- before - 在n次之前调用才生效 - (次数, 函数) - 包装后函数
- after - 在n次之后调用才生效 - (次数, 函数) - 包装后函数
- ary - 舍去第n个后的参数 - (函数, 最大参数数) - 包装后函数
- flow - 依次调用函数 - (函数数组) - 包装后函数
- flowRight/backFlow/compose - 倒序调用函数 - (函数数组) - 包装后函数
- bind - 同Function.prototype.bind - (函数, 绑定this对象, 默认调用实参) - 包装后函数 - 默认实参以rest args形式传入
- bindAll - 对象成员方法全部bind到对象本身 - (对象, [特定函数名数组]) - 包装后对象
- bindKey - key对应的method bind到对象 - (对象, method key, 默认调用实参数组) - 包装后函数
- curry - 柯里化 - (函数, [参数数]) - 包装后函数 - 返回的函数非常灵活, 可以传入<=arguments.length以内的任意实参, 也可以使用'_'作为placeholder,后续填充实参
- curryRight - 逆向传实参柯里化 - (函数, [实参数]) - 包装后函数
- debounce - 函数去抖,详见<a href="//ywbdxx.win/?p=198">这里</a> - (函数, [延时=0], [选项]) - 去抖后函数 - 选项有leading,trailing,maxWait属性可选
- throttle - 函数节流,详见<a href="//ywbdxx.win/?p=198">这里</a> - (函数, [延时=0], [选项]) - 节流后函数 - 选项有leading,trailing属性可选
- defer - setTimeout(func,0)简写版 - (函数, [实参rest arg]) - setTimeout id
- delay - setTimeout高级版 - (函数, 延时毫秒数, [实参rest arg]) - setTimeout id
- memoize - 缓存 - (函数, [缓存键处理函数]) - 带cache属性的函数 - 1.慎用
- wrap - 包装器 - (函数, 包装函数) - 包装后函数 - 包装函数的参数为原函数和原函数的参数
- modArgs - 实参转换器 - (函数, [...转换函数]) - 带参数转换的函数 - 每个转换函数对应一个实参
- negate - 返回值取反器 - (函数) - 返回值去反后的函数
- once - 只执行一次包装器(相当于_.before(1)) - (函数) - 包装后函数
- partical - 偏函数包装器 - (函数, [...实参列表或placeholder]) - 偏函数 - 科普: 偏函数是已经对原函数设置特定实参的函数
- particalRight - 偏函数包装器(实参从右开始赋值) - (函数, [...实参列表或placeholder]) - 偏函数
- rearg - 参数顺序调整器 - (函数, [...形参的实际顺序索引]) - 包装后函数
- spread - 数组实参转列表 - (函数) - 包装后函数 - 传入的数组将会被转换为...arguments形式
<h4>惰性求值包装器</h4>
- _ - 包装对象或集合 - (对象|数组) - lodash对象 | 注: 使用value()求值, 如果结果为单个自动求值
- chain - 包装对象或集合 - (对象|数组) - lodash对象 | 注: 用于惰性求值等函数式特性, 显式调用value()求值
- tap - 拦截器 - 函数 - 函数实参 - 有副作用
- thru - 拦截器 - 函数 - 函数返回值 - 无副作用
- prototype.chain - 同chain,可在包装后调用 - () - lodash对象
- prototype.commit - 提交求值 - () - lodash对象
- prototype.concat - 包装器中加入新的集合 - (...集合) - lodash对象
- prototype.plant - 移植包装器到其他集合 - (集合) - lodash对象
- prototype.reverse - 倒转包装器中的集合 - () - lodash对象
- prototype.value/run/toJson/valueOf - 显式求值 - () - 集合
- prototype.toString - 显式求值后转换为字符串,数组默认join(',') - () - 字符串]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[ES6的变化与新特性(下篇)]]></title>
            <link>https://code2life.top/blog/0007-es6-change-p2</link>
            <guid>https://code2life.top/blog/0007-es6-change-p2</guid>
            <pubDate>Sun, 03 Dec 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="es6的变化与新特性-下篇" tabindex="-1">ES6的变化与新特性(下篇) <a class="header-anchor" href="#es6的变化与新特性-下篇" aria-label="Permalink to &quot;ES6的变化与新特性(下篇)&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="part3-set-map-iterator" tabindex="-1">Part3. Set &amp; Map &amp; Iterator <a class="header-anchor" href="#part3-set-map-iterator" aria-label="Permalink to &quot;Part3. Set &amp; Map &amp; Iterator&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="set数据结构" tabindex="-1">Set数据结构 <a class="header-anchor" href="#set数据结构" aria-label="Permalink to &quot;Set数据结构&quot;">&ZeroWidthSpace;</a></h4>
<p>ES6新增了Set数据结构, 其相关知识如下:</p>
<ol>
<li>Set相当于值唯一的数组, 构造器参数为<span style="color: #ff0000;">数组</span>或<span style="color: #ff0000;">类数组</span>对象</li>
<li>可用于数组去重:<span style="color: #ff0000;"> [...new Set(array)]</span></li>
<li>具有<span style="color: #ff0000;">size</span>属性</li>
<li>Set有以下方法
<span style="color: #ff0000;">add,delete,has,clear: </span>add返回<span style="color: #ff0000;">Set本身</span>,可以链式调用. delete,has返回<span style="color: #ff0000;">true/false</span>
<span style="color: #ff0000;">keys,values,entries,forEach: </span>keys,values,entries返回<span style="color: #ff0000;">Symbol.iterator</span>遍历器entries遍历实体是数组,index 0为key,index 1为value在Set中key-value是<span style="color: #ff0000;">相同</span>的,entries中[0]和[1]也是相同的forEach参数函数的参数分别是value,key</li>
<li>Array.from可以转换Set为数组, 或者[...set]</li>
<li>Set可以直接使用<span style="color: #ff0000;">for...of</span>遍历</li>
</ol>
<p>利用Set实现并交差集</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> union </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set1,</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set2]); </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> intersect </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set1].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">filter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(x </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt; set2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">has</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(x)));</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> diff </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set1].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">filter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(x </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt; </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">set2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">has</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(x)));</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h4 id="weakset与weakmap" tabindex="-1">WeakSet与WeakMap <a class="header-anchor" href="#weakset与weakmap" aria-label="Permalink to &quot;WeakSet与WeakMap&quot;">&ZeroWidthSpace;</a></h4>
<ul>
<li>WeakSet: 成员<span style="color: #ff0000;">只能是对象</span>,<span style="color: #ff0000;">无size属性和遍历器,只能add,delete,has,不能clear</span>.  WeakSet中对象的引用不计数,即<span style="color: #ff0000;">弱引用</span>,无需考虑内存泄漏, 用法与Set一样</li>
<li>WeakMap: 成员<span style="color: #ff0000;">只能是对象</span>,与WeakSet类似,只有<span style="color: #ff0000;">get,set,has,delete</span>可以用</li>
</ul>
<h4 id="map数据结构" tabindex="-1">Map数据结构 <a class="header-anchor" href="#map数据结构" aria-label="Permalink to &quot;Map数据结构&quot;">&ZeroWidthSpace;</a></h4>
<ol>
<li>Map相当于键可以为<span style="color: #ff0000;">任意类型</span>的<span style="color: #ff0000;">属性有序</span>的Object</li>
<li>构造器接收数组为参数,参数为<span style="color: #ff0000;">[[key,value],[key,value]]</span>形式</li>
<li>Map有<span style="color: #ff0000;">size</span>属性</li>
<li>Map有以下方法
<span style="color: #ff0000;">set,get,has,delete,clear</span>调用方式和返回值与Set类似</li>
<li>遍历器有<span style="color: #ff0000;">keys,values,entries,forEach</span>,与Set类似,前三个都可以使用<span style="color: #ff0000;">for...of</span>遍历
entries遍历实体是数组<span style="color: #ff0000;">,index 0为key,index 1为value</span></li>
<li>Map也可以使用...运算符加[],扩展为构造器参数形式的数组</li>
</ol>
<h4 id="ecmascript6中的iterator" tabindex="-1">ECMAScript6中的Iterator <a class="header-anchor" href="#ecmascript6中的iterator" aria-label="Permalink to &quot;ECMAScript6中的Iterator&quot;">&ZeroWidthSpace;</a></h4>
<p>遍历Iterator每一次调用next方法, 都会返回数据结构的当前成员的信息.具体来说,就是返回一个包含<span style="color: #ff0000;">value和done</span>两个属性的对象.其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束. 凡是部署了<span style="color: #ff0000;">Symbol.iterator属性</span>的数据结构,就称为部署了遍历器接口.调用这个接口,就会返回一个遍历器对象.也可以使用for...of遍历<br>
另外, ES6新增的一个基本数据类型Symbol, 作用类似于创建一个唯一的GUID, 并且对相同的键(Symbol.for())返回相同的标识值, 作为属性时不会被遍历到, 详见<a href="//es6.ruanyifeng.com/#docs/symbol" target="_blank" rel="noreferrer">此处</a></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 不同人群遍历js数组的办法 */</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//小学生级别</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'b'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'c'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">of</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//a b c</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//初中生级别</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> it </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr[Symbol.iterator]();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">it.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'a', done: false }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">it.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'b', done: false }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">it.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'c', done: false }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">it.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: undefined, done: true }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//专家级用法, 自定义遍历器</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> iterable </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'b'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'c'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  length: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  [Symbol.iterator]: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[Symbol.iterator]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> item </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">of</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> iterable) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(item); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 'a', 'b', 'c'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 注: 通用做法是部署遍历器函数返回this,并在对象中定义返回value和done的next方法</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 亦可直接部署遍历器函数直接返回带next方法的对象</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * [Symbol.iterator]() { return this;} </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * this.next = next(){ return { value : obj, done : false } }</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> */</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br></div></div><h2 id="part4-generator函数" tabindex="-1">Part4. Generator函数 <a class="header-anchor" href="#part4-generator函数" aria-label="Permalink to &quot;Part4. Generator函数&quot;">&ZeroWidthSpace;</a></h2>
<blockquote>
<p>Generator是ECMAScript6中提供的新特性.在过去,封装一段运算逻辑的单元是函数.函数只存在没有被调用或者被调用的情况,不存在一个函数被执行之后还能暂停的情况,而Generator的出现让这种情况成为可能, 即<span style="color: #ff0000;">函数能执行到yield暂停, 调用next函数生成一个值并执行到下个yield或return.</span></p>
</blockquote>
<h4 id="基本用法" tabindex="-1">基本用法 <a class="header-anchor" href="#基本用法" aria-label="Permalink to &quot;基本用法&quot;">&ZeroWidthSpace;</a></h4>
<p>调用Generator函数，返回一个<span style="color: #ff0000;">遍历器对象</span>[Symbol.iterator]，代表Generator函数的内部指针。以后，每次调用遍历器对象的next方法，就会返回一个有着<span style="color: #ff0000;">value和done</span>两个属性的对象。value属性表示当前的<span style="color: #ff0000;">内部状态的值</span>，是yield语句后面那个<span style="color: #ff0000;">表达式的值</span>, 或<span style="color: #ff0000;">next函数传入的参数</span>；done属性是一个布尔值，表示<span style="color: #ff0000;">是否遍历结束</span>。</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> helloWorldGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'hello'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'world'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'ending'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> hw </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> helloWorldGenerator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">hw.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'hello', done: false }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">hw.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'world', done: false }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">hw.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: 'ending', done: true }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">hw.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// { value: undefined, done: true }</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p><em>注意点</em></p>
<ul><li>yield 不能用在普通函数中, 只能在带 * 的函数内使用</li><li>yield 用在表达式中, 必须加括号. 并且包含表达式的语句在下一个yield才会执行. 栗子:console.log(1 + (yield 233));</li><li>yield 后的返回值即next()的value, 如果没有return语句, 最后一次next()返回值是undefined, 否则value是返回值</li><li>在函数参数和赋值表达式中, yield可以不加括号</li><li>如果在next()中传入参数, 则yield表达式的返回值强制变成该参数</li><li>如果需要在Generator函数yield另一个Generator函数, 可以写成yield* xx();</li><li>Generator函数只能call不能new</li><li>Generator函数中的this与返回的迭代函数this不一致</li></ul>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'x'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//等价于上面的写法</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function*</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'x'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">of</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    yield</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  yield</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'y'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h3 id="使用generator函数同步化异步操作" tabindex="-1">使用Generator函数同步化异步操作 <a class="header-anchor" href="#使用generator函数同步化异步操作" aria-label="Permalink to &quot;使用Generator函数同步化异步操作&quot;">&ZeroWidthSpace;</a></h3>
<blockquote>
<p>Generator本身是一种半协程的实现, 能够在<span style="color: #ff0000;">函数执行过程中动态改变执行权</span>, 如果在将异步操作yield在一个Generator函数中, 每个<span style="color: #ff0000;">异步操作完成后自动的调用next()</span>, 即可实现看起来像同步代码的异步操作了</p>
</blockquote>
<p>膜拜TJ大神的代码: <a href="https://github.com/tj/co/blob/master/index.js" target="_blank">co模块</a> 实现自动执行Generator的核心只有几十行, 其中最关键的是这几行</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* co模块是一个Generator函数包装器, 自动执行异步的Generator, 返回一个Promise对象 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onFulfilled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ret;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ret </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> gen.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(res);  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//调用next, 获取next方法返回的 {value, done}对象, 并且给yield的返回值置为返回的Promise对象</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (e) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> reject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(e);     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果Generator函数执行过程任意next()中出现异常, 都能catch到</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ret);    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//调用核心next方法, 递归</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">ret</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (ret.done) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ret.value);  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果Generator next()执行完毕, 将Promise置为resolve态</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> toPromise.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ctx, ret.value); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//如果yield了非Promise对象则转换成为Promise对象,如普通对象和thunk函数</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> isPromise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(onFulfilled, onRejected);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onRejected</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> TypeError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'You may only yield a function, promise, generator, array, or object, '</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        +</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'but the following object was passed: "'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(ret.value) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '"'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h2 id="part5-异步编程" tabindex="-1">Part5 异步编程 <a class="header-anchor" href="#part5-异步编程" aria-label="Permalink to &quot;Part5 异步编程&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="异步的本质" tabindex="-1">异步的本质 <a class="header-anchor" href="#异步的本质" aria-label="Permalink to &quot;异步的本质&quot;">&ZeroWidthSpace;</a></h4>
<blockquote>
<p>在浏览器中,每个window中, 一般JS执行引擎是一个线程,DOM渲染是一个线程, 事件循环是一个线程, 不同的浏览器可能有不同的多线程策略, 比如DOM渲染可能与JS执行是一个线程, 但JS执行环境始终是单线程的;
在Node.js中, Event Loop是一个线程, I/O是一个线程池(windows中是IOCP, *nux中是自主线程池), JS执行环境也是一个单线程;
<span style="color: #ff0000;">基于EventLoop的非阻塞式设计决定了无论是浏览器还是后端, I/O, 网络等非CPU计算型操作必须是异步的.</span></p>
</blockquote>
<h4 id="异步编程的方式" tabindex="-1">异步编程的方式 <a class="header-anchor" href="#异步编程的方式" aria-label="Permalink to &quot;异步编程的方式&quot;">&ZeroWidthSpace;</a></h4>
<ol><li>回调Hell(最原始的方式, 不进行详述)</li><li>发布/订阅 事件</li><li>Promise规范</li><li>第三方流程控制库</li><li>Generator函数与async/await</li></ol>
<h4 id="_1-事件监听-发布-订阅" tabindex="-1">1. 事件监听(发布/订阅) <a class="header-anchor" href="#_1-事件监听-发布-订阅" aria-label="Permalink to &quot;1. 事件监听(发布/订阅)&quot;">&ZeroWidthSpace;</a></h4>
<p>在回调层数很少的情况下, <span style="color: #ff0000;">直接使用回调是</span>最好的方式, 效率最高, 语义最明确, 其次是使用<span style="color: #ff0000;">事件监听</span>来实现.事件机制是JS的核心, 但是在业务逻辑中大量使用事件来处理异步操作会造成<span style="color: #ff0000;">代码逻辑不清晰, 难以阅读和定位</span>. 某些适合<span style="color: #ff0000;">事件队列</span>的情况下, 使用事件的实现方式可能更加适合. 比如下面这个栗子.</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> events </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'events'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> proxy </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> events.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">EventEmitter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "ready"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> select</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  proxy.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">once</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"selected"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, callback);    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//订阅事件, 一旦查询完成执行callback</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "ready"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {            </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//确保同一时间只调用一次查询</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "pending"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    db.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">select</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"SQL"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">results</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      proxy.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">emit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"selected"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, results); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//完成查询emit事件</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "ready"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h4 id="_2-promise-deferred" tabindex="-1">2. Promise/Deferred <a class="header-anchor" href="#_2-promise-deferred" aria-label="Permalink to &quot;2. Promise/Deferred&quot;">&ZeroWidthSpace;</a></h4>
<p>Promise是ES6和CommonJS中的标准和规范. 简单说<span style="color: #ff0000;">Promise是一个容器, 里面保存着某个未来才会结束的事件</span>(通常是一个异步操作)的结果.从语法上说,Promise是一个对象,从它可以获取异步操作的消息.Promise提供统一的API(then,catch...),各种异步操作都可以用同样的方法进行处理.
Deferred对象用于内部, 维护异步模型的状态, 是实现<span style="color: #ff0000;">Promise规范的一种方式</span>. 在jQuery1.5版本后, 使用$.Deferred()可以创建一个Deferred对象, Deferred对象拥有then, resolve, reject等等函数, 使用Deferred.promise()可以得到对应的Promise对象.进而链式调用then, done, fail等函数.
Promise的基本用法如下</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> promise </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">reject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //code...</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (success){</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(value);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    reject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(error);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">promise.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // success</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // failure</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 与上面的代码等价 */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">promise.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // success</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // failure</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><ul>
<li>Promise.all方法<br>
传入Promise对象数组并行Promise, 当所有Promise都为resolve状态或某个Promise为reject状态时改变整个Promise的状态</li>
<li>Promise.race方法<br>
传入Promise对象数组并行Promise, 在首次发生状态变化时改变整个Promise的状态</li>
<li>Promise.resolve/reject<br>
直接返回一个状态为resolve/reject的Promise对象, 调用的参数会传入回调函数中
常用的Promise的实现有: q, bluebird, ES6原生实现等等, 性能和提供的API会有差异</li>
</ul>
<h4 id="_3-第三方库" tabindex="-1">3. 第三方库 <a class="header-anchor" href="#_3-第三方库" aria-label="Permalink to &quot;3. 第三方库&quot;">&ZeroWidthSpace;</a></h4>
<p>第三方异步流程控制库非常多, 其中使用最广泛的是<span style="color: #ff0000;">async</span>库, 详细文档传送门: <a href="//caolan.github.io/async/" target="_blank">点这里</a>
其中常用的API示例如下</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//异步迭代器</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">each</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(openFiles, saveFile, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // if any of the saves produced an error, err would equal that error</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//异步映射, results为map后的集合</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'file1'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'file2'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'file3'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], fs.stat, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">results</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // results is now an array of stats for each file</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//异步规约, arguments[1]是memo, 即规约集</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reduce</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">memo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // pointless async:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">nextTick</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, memo </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> item)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">result</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // result is now equal to the last value of memo, which is 6</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//串行</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">series</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // do some stuff ...</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'one'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // do some more stuff ...</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'two'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// optional callback</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">results</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // results is now equal to ['one', 'two']</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//并行, 如需限制并发数, 使用parallelLimit</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">parallel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    one</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">            callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    two</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        setTimeout</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">            callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">results</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // results is now equals to: {one: 1, two: 2}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//瀑布流, 参数往下传递, 直到完成整个异步的callback</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">waterfall</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'one'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'two'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">arg1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">arg2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // arg1 now equals 'one' and arg2 now equals 'two'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'three'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">arg1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // arg1 now equals 'three'</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        callback</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'done'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">result</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // result now equals 'done'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//自动处理前置依赖, 此例showData一定在readData后发生</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">async.auto</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br></div></div><h4 id="_4-generator函数与async-await" tabindex="-1">4. Generator函数与async/await <a class="header-anchor" href="#_4-generator函数与async-await" aria-label="Permalink to &quot;4. Generator函数与async/await&quot;">&ZeroWidthSpace;</a></h4>
<p>Generator函数的使用和co模块实现异步操作同步化的代码在Part4中已经有描述, 然而类似Python语法并且引入新的符号(*)的Generator函数并没有普及使用,<br>
<strong>在ES7提案中, 一种新的异步解决方案出现了, 那就是<span style="color:red">async/await</span></strong>
async/await的使用与普通的函数几乎没有区别, 只需要加上对应的关键字, 虽然其底层是一个包含了自动执行器的Generator函数, 通过Promise控制自动执行. 这种方便且易于理解的异步编程方式目前已经在node.js中原生支持, Babel等转码工具也早已可以使用. async/await目前已经成为最主流的异步控制方式. 使用async/await方式还有一个好处就是当出现错误时可以直接用try...catch捕捉到, 这是其他异步控制方式无法办到的.</p>
<p><strong>大道至简, 看似无异步, 处处是异步</strong></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> aFunctionReturnPromise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">reject</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">''</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> asyncFunc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> aFunctionReturnPromise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[ES6的变化与新特性(上篇)]]></title>
            <link>https://code2life.top/blog/0006-es6-change-p1</link>
            <guid>https://code2life.top/blog/0006-es6-change-p1</guid>
            <pubDate>Fri, 01 Dec 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="es6的变化与新特性-上篇" tabindex="-1">ES6的变化与新特性(上篇) <a class="header-anchor" href="#es6的变化与新特性-上篇" aria-label="Permalink to &quot;ES6的变化与新特性(上篇)&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="part1-变量声明与作用域的变化" tabindex="-1">Part1. 变量声明与作用域的变化 <a class="header-anchor" href="#part1-变量声明与作用域的变化" aria-label="Permalink to &quot;Part1. 变量声明与作用域的变化&quot;">&ZeroWidthSpace;</a></h2>
<p>ES6有6种姿势声明变量: var let const function class import<br>
其中<span style="color: #ff0000;">let</span>关键字，用来替代var声明变量。它的用法类似于var，但是所声明的变量，<span style="color: #ff0000;">只在let命令所在的代码块内有效</span>,即{}内有效.let声明的变量<span style="color: #ff0000;">不存在变量提升</span></p>
<h4 id="var与let的对比" tabindex="-1">var与let的对比 <a class="header-anchor" href="#var与let的对比" aria-label="Permalink to &quot;var与let的对比&quot;">&ZeroWidthSpace;</a></h4>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">lt; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    a[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//10;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//--------------------</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">lt; </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    a[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(i);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    };</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">6</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">](); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 6  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//在for循环声明的let变量只在该循环块内有效,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//并且每次循环会重新分配,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//故在新的循环轮次中i是新的变量</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h4 id="变量声明的暂时性死区" tabindex="-1">变量声明的暂时性死区 <a class="header-anchor" href="#变量声明的暂时性死区" aria-label="Permalink to &quot;变量声明的暂时性死区&quot;">&ZeroWidthSpace;</a></h4>
<p>let变量绑定的代码块不受外部影响,在<span style="color: #ff0000;">代码块开始处</span>到声明<span style="color: #ff0000;">该变量声明处</span>存在暂时性死区,这将导致该部分使用<span style="color: #ff0000;">暂未声明的变量报错</span>,即使全局存在<span style="color: #ff0000;">同名变量也报错</span>.const同理.</p>
<p>栗子</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    /*此处如果用var声明,会报重复声明的错误,</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      ES6不允许let和const同作用域重复声明*/</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    tmp </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'abc'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;              </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tmp);         </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//报错:变量未定义</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tmp;               </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//报错:同上</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tmp;                  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//死区结束</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tmp);         </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// undefined</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    tmp </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 123</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(tmp);         </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 123</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h4 id="const关键字" tabindex="-1">const关键字 <a class="header-anchor" href="#const关键字" aria-label="Permalink to &quot;const关键字&quot;">&ZeroWidthSpace;</a></h4>
<p>const用于声明常量,其值或指向的<span style="color: #ff0000;">对象引用不能改变</span>;<br>
const作用域规则同let<span style="color: #ff0000;">,不存在变量提升,存在暂时性死区</span>;<br>
const声明的<span style="color: #ff0000;">同时必须赋值</span>,'const a;a=1;console.log(a)'这在非严格模式下输出undefined,严格模式报错;<br>
const声明的对象引用不可变,但<span style="color: #ff0000;">对象属性可变<span style="color: #000000;">;</span></span><br>
跨模块常量需要用<span style="color: #ff0000;">export</span>声明 export const xx = xx;</p>
<h4 id="函数作用域" tabindex="-1">函数作用域 <a class="header-anchor" href="#函数作用域" aria-label="Permalink to &quot;函数作用域&quot;">&ZeroWidthSpace;</a></h4>
<p>ES5 : 存在函数提升,在代<span style="color: #ff0000;">码块内的函数</span>会提升到<span style="color: #ff0000;">整个函数作用域</span>内<br>
ES5 严格模式 : 函数<span style="color: #ff0000;">只能在函数内或顶级作用域声明</span>, 不能再if{} for{}等块内声明<br>
ES6 : 函数<span style="color: #ff0000;">不存在提升</span>, if{} for{}等块内声明的函数<span style="color: #ff0000;">只在块内有效</span></p>
<h2 id="part2-解构" tabindex="-1">Part2. 解构 <a class="header-anchor" href="#part2-解构" aria-label="Permalink to &quot;Part2. 解构&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="es6允许按照一定模式-从数组和对象中提取值-对变量进行赋值-这被称为解构-destructuring" tabindex="-1">ES6允许按照一定模式，从<span style="color: #ff0000;">数组和对象中提取值，对变量进行赋值</span>，这被称为解构(Destructuring). <a class="header-anchor" href="#es6允许按照一定模式-从数组和对象中提取值-对变量进行赋值-这被称为解构-destructuring" aria-label="Permalink to &quot;ES6允许按照一定模式，从&lt;span style=&quot;color: #ff0000;&quot;&gt;数组和对象中提取值，对变量进行赋值&lt;/span&gt;，这被称为解构(Destructuring).&quot;">&ZeroWidthSpace;</a></h4>
<ul><li>允许<span style="color: #ff0000;">不完全解构</span></li><li>解构可运用于<span style="color: #ff0000;">var let const import</span></li><li>对于<span style="color: #ff0000;">Set</span>结构，也可以使用数组的解构赋值。eg: let [x, y, z] = new Set(["a", "b", "c"])</li><li><span style="color: #ff0000;">Generator</span>函数也能解构,赋值过程依次调用函数获取值</li><li>解构<span style="color: #ff0000;">允许附带默认值</span>,生效条件是匹配到<span style="color: #ff0000;">严格undefined</span> eg: let [a=1,b=3] = [2]  //a=2,b=3</li><li>解构比较过程是<span style="color: #ff0000;">严格相等</span>运算 eg: let [a=undefined] = [null] //a=null 因为 undefined!==null</li><li><span style="color: #ff0000;">对象</span>的解构赋值的内部机制，是先找到<span style="color: #ff0000;">同名属性</span>，然后再赋给<span style="color: #ff0000;">对应的变量</span>。真正被赋值的是<span style="color: #ff0000;">后者</span>，即属性名称只是作为匹配模式</li><li><span style="color: #ff0000;">已经声明的对象</span>解构赋值最好<span style="color: #ff0000;">加上()</span>以避免解释器当成代码块 eg : var x; {x} = {x: 1}; //如果解构语句在行首则报错</li><li>解构声明等同于分别声明, let和const关键字声明的同区块<span style="color: #ff0000;">不能出现重复</span></li><li><span style="color: #ff0000;">对象</span>解构时, 若源为基本类型,会<span style="color: #ff0000;">转换为对象类型</span>; <span style="color: #ff0000;">数组</span>解构时, 源必须是<span style="color: #ff0000;">可遍历的结构或字符串</span></li><li><span style="color: #ff0000;">函数参数</span>也能解构, <span style="color: #ff0000;">形参</span>被解构赋值为<span style="color: #ff0000;">实参</span>, eg : function foo([x,y]) {};foo([1,2])</li><li>解构模式<span style="color: #ff0000;">内部不要有括号</span>()</li>
</ul>
<p>示例</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { bar, foo } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { foo: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"aaa"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, bar: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"bbb"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这是简写形式.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: foo, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: bar } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { foo: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"aaa"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, bar: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"bbb"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//真·解构</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [x, { </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">y</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : z }] } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// x='Hello'; p和y 是模式,未声明; z = 'World'   EX·嵌套·解构</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    p: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">        "Hello"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        { y: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"World"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, z : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"GG"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [foo, [[bar], baz]] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, [[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">foo </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">bar </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">baz </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [ , , third] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"foo"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"bar"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"baz"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">third </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// "baz"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [x, , y] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">x </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">y </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [head, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">tail] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//...运算符不能放在前面,含义为剩下的参数</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">head </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">tail </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// [2, 3, 4]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [x, y, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">z] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">x </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// "a"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">y </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// undefined</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">z </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// []</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br></div></div><p>用途</p>
<ol><li>交换变量 [a,b] = [b,a]</li><li>提取数据, 从返回值提取或从Json提取等等</li><li>函数默认参数</li><li>加载模块指定值 eg: import {A,B} from './xx';</li></ol>
<h2 id="part3-内置对象扩展与字符串模板" tabindex="-1">Part3. 内置对象扩展与字符串模板 <a class="header-anchor" href="#part3-内置对象扩展与字符串模板" aria-label="Permalink to &quot;Part3. 内置对象扩展与字符串模板&quot;">&ZeroWidthSpace;</a></h2>
<h4 id="字符串和正则的扩展" tabindex="-1">字符串和正则的扩展 <a class="header-anchor" href="#字符串和正则的扩展" aria-label="Permalink to &quot;字符串和正则的扩展&quot;">&ZeroWidthSpace;</a></h4>
<ul><li>ES6 unicode: Unicode编码放在大括号内,对于&gt;0xFFFF的字符不会错误解释<span style="color: #ff0000;"> \u{xxxxx}</span></li><li>codePointAt() JS字符以UTF-16的格式储存，每个字符固定为2个字节。对于那些需要4个字节储存的字符,字符长度为2,此方法避免该问题, 返回, 返回字符码点, <span style="color: #ff0000;">扩展charCodeAt</span>方法</li><li>fromCodePoint([...code]) 返回Unicode码的对应字符,<span style="color: #ff0000;">扩展fromCharCode</span>方法</li><li>at() 返回指定位置字符, <span style="color: #ff0000;">扩展charAt</span>方法</li><li>normalize() 统一同一个字符的不同Unicode</li><li><span style="color: #ff0000;"> includes(), startsWith(), endsWith()</span> 终于有这三个方法了,第一个参数是匹配字符,第二个参数是<span style="color: #ff0000;">搜索开始位置</span></li><li>repeat() 重复字符串,参数是自然数</li><li>模板字符串 终于出来了,<span style="color: #ff0000;">here doc</span>写法</li></ul>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//基本用法, &#x3C;span style="color: #ff0000;">反引号&#x3C;/span>包含</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">Here</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">is</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">doc</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/**</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 模板替换</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * ${}中的变量会被替换成实际值;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 非字符串变量会自动调用toString()后替换;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 表达式会计算值后替换;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> * 函数会替换为其返回值</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> number </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">str </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> `Test ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">number</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">} Test`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//Test 1 Test</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> bar </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {foo : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> };</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){};</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`foo ${</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">foo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">()</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">} ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">bar</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">foo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//foo undefined 1</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//标签模板(一种函数调用)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">tag</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`Hello ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> } world ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> } `</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> tag</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]);      </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//['Hello ', ' world ', ' ']</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]);     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//15</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]);    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//15</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//String.raw转义模板字符串</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">String.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">raw</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`Hi</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">${</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}!`</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  //Hi\\n5</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><h4 id="数组扩展" tabindex="-1">数组扩展 <a class="header-anchor" href="#数组扩展" aria-label="Permalink to &quot;数组扩展&quot;">&ZeroWidthSpace;</a></h4>
<p><span style="color: #ff0000;">Array.from</span>将任何类数组或实现Iterator的类型转换为<span style="color: #ff0000;">真·数组</span></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({length : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//[undefined,undefined,undefined]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">([</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],x</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt;x</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//[2,3] 接受第二个参数进行map, == Array.from([1,2]).map(x=&#x26;gt;x+1);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({length : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">},()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.a,{a:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//[1,1,1] 接受第三个参数绑定this</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p><span style="color: #ff0000;">Array.of</span>将<span style="color: #ff0000;">从参数构造数组</span>,行为等同于new Array()参数&gt;2时,不存在重载,如Array.of(1,2),返回[1,2]
Array.copyWithin 没卵用..<br>
<span style="color: #ff0000;">Array.find()和findIndex()</span>, 接受一个函数,返回执行至第一个返回true时的value或者index,接受的函数参数:value, index, arr <span style="color: #ff0000;">弥补indexOf方法, indexOf方法无法识别数组的NaN成员</span>，但是findIndex方法可以借助Object.is方法做到,<br>
<span style="color: #ff0000;">Array.includes</span>, 返回bool, 相当于可以识别NaN的indexOf === -1<br>
<span style="color: #ff0000;">Array.fill()</span>, 填充值,不多说<br>
<span style="color: #ff0000;">entries()，keys()和values()</span> 返回三种遍历器</p>
<h4 id="函数扩展" tabindex="-1">函数扩展 <a class="header-anchor" href="#函数扩展" aria-label="Permalink to &quot;函数扩展&quot;">&ZeroWidthSpace;</a></h4>
<p>ES6允许函数拥有<span style="color: #ff0000;">默认值</span>, 并且可以和<span style="color: #ff0000;">解构赋值同时使用</span></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">method</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'GET'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {}){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(method);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"test1"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//GET</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"test2"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,{method : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"PUT"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//PUT</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><span style="color: #ff0000;">函数拥有length属性</span>,含义是预期传入参数个数,不包括默认参数和rest参数, name属性也被标准化</p>
<h4 id="扩展运算符" tabindex="-1">扩展运算符(...) <a class="header-anchor" href="#扩展运算符" aria-label="Permalink to &quot;扩展运算符(...)&quot;">&ZeroWidthSpace;</a></h4>
<p>在形参列表作用是将剩余变量加入...variable数组中,避免arguments对象的使用(注:只能在参数列表最后出现);
在实参列表作用是将数组转换为参数序列(相当于apply的第二个参数用法);<br>
另外,扩展运算符也可以合并数组;配合解构生成数组;转换成为数组(相当于Array.from)</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//[2,3]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">c</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a,b,c);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1,2,3</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><h4 id="lambda表达式" tabindex="-1">Lambda表达式 <a class="header-anchor" href="#lambda表达式" aria-label="Permalink to &quot;Lambda表达式&quot;">&ZeroWidthSpace;</a></h4>
<p><span style="color: #ff0000;">ES6提供了对lambda表达式</span>支持, 又名箭头函数, 格式为 &quot;(/* 参数 <em>/) =&gt; {/</em> 函数体 */}&quot;, 以下是其注意点,与匿名函数最大的区别是固化this:<br>
（1）函数体内的this对象，就是定义时所在的对象，而不是使用时所在的对象<br>
（2）不可以当作构造函数，也就是说，不可以对lambda表达式使用new命令，否则会抛出一个错误<br>
（3）不可以使用arguments对象，该对象在函数体内不存在。如果要用，可以用Rest参数代替<br>
（4）不可以使用yield命令，因此箭头函数不能用作Generator函数<br>
关于尾调用优化和尾递归柯里化等函数式编程概念, 以后详述</p>
<h4 id="对象扩展" tabindex="-1">对象扩展 <a class="header-anchor" href="#对象扩展" aria-label="Permalink to &quot;对象扩展&quot;">&ZeroWidthSpace;</a></h4>
<p>对象有简洁写法, 不写属性值时, 属性值为属性名称变量的值<br>
对象声明<span style="color: #ff0000;">允许属性名表达式</span>, 代替 obj[expr]的写法</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;b</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({a,b}); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//{a : 1 , b : 2}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    [a] : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()] : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'b'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(c); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//{ '1' : 'a' , foo()的返回值 : 'b'}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p><span style="color: #ff0000;">Object.is</span>方法, 接受两个参数, 判断两个对象是否相同,这是一种Same-value equality, 相当于可以判断NaN===NaN和+0!==-0的严格等于运算符<br>
<span style="color: #ff0000;">Object.assign</span>方法, 与_.extent $.extend作用相同,合并对象(注:采用浅拷贝)
ES7提出扩展运算符'...'引入对象, 这已经在Babel实现
Object.keys , Object.values, Object.entries等等其他语法糖不详述.</p>
<h4 id="es6中可用的遍历对象的6种姿势" tabindex="-1">ES6中可用的遍历对象的6种姿势 <a class="header-anchor" href="#es6中可用的遍历对象的6种姿势" aria-label="Permalink to &quot;ES6中可用的遍历对象的6种姿势&quot;">&ZeroWidthSpace;</a></h4>
<ul><li>for...in 循环遍历对象自身的和继承的可枚举属性（不含Symbol属性）</li><li>Object.keys(obj) 返回一个数组，包括对象自身的（不含继承的）所有可枚举属性（不含Symbol属性）</li><li>Object.getOwnPropertyNames(obj) 返回一个数组，包含对象自身的所有属性（不含Symbol属性，但是包括不可枚举属性）</li><li>Object.getOwnPropertySymbols(obj) 返回一个数组，包含对象自身的所有Symbol属性</li><li>Reflect.ownKeys(obj) 返回一个数组，包含对象自身的所有属性，不管是属性名是Symbol或字符串，也不管是否可枚举</li><li>Reflect.enumerate(obj) 返回一个Iterator对象，遍历对象自身的和继承的所有可枚举属性（不含Symbol属性），与for...in循环相同。</li></ul>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[伪元素实现自定义滚动条]]></title>
            <link>https://code2life.top/blog/0005-pseudo-class-scrollbar</link>
            <guid>https://code2life.top/blog/0005-pseudo-class-scrollbar</guid>
            <pubDate>Wed, 29 Nov 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="伪元素实现自定义滚动条" tabindex="-1">伪元素实现自定义滚动条 <a class="header-anchor" href="#伪元素实现自定义滚动条" aria-label="Permalink to &quot;伪元素实现自定义滚动条&quot;">&ZeroWidthSpace;</a></h1>
<h2 id="从伪类和伪元素说起"><a href="#从伪类和伪元素说起" class="headerlink" title="从伪类和伪元素说起"></a>从伪类和伪元素说起</h2><blockquote>
<p>CSS3中规定:伪类(Pseodu-classes)用<span style="color: #ff0000;">一个冒号</span>来表示，而伪元素(Pseodu-elements)则用<span style="color: #ff0000;">两个冒号</span>来表示.(低版本IE不支持单冒号)<br><strong>区别 :</strong> 伪类的效果可以通过添加一个实际的<span style="color: #ff0000;">类</span>来达到，而伪元素的效果则需要通过添加一个实际的<span style="color: #ff0000;">元素</span>才能达到, 伪元素无法使用DOM操作控制, 而伪元素能实现的功能基本可以使用实元素实现, 一般情况下尽量不要使用, 增加了维护的难度.</p>
</blockquote>
<h4 id="常用的伪类"><a href="#常用的伪类" class="headerlink" title="常用的伪类"></a>常用的伪类</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">:active      //向被激活的元素添加样式</span><br><span class="line">:focus       //向拥有键盘输入焦点的元素添加样式</span><br><span class="line">:hover       //当鼠标悬浮在元素上方时，向元素添加样式</span><br><span class="line">:link        //向未被访问的链接添加样式</span><br><span class="line">:visited     //向已被访问的链接添加样式</span><br><span class="line">:first-child //向元素的第一个子元素添加样式</span><br><span class="line">:lang        //向带有指定 lang 属性的元素添加样式</span><br></pre></td></tr></tbody></table></figure>
<h4 id="常用的伪元素"><a href="#常用的伪元素" class="headerlink" title="常用的伪元素"></a>常用的伪元素</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">::first-letter //向文本的第一个字母添加特殊样式</span><br><span class="line">::first-line   //向文本的首行添加特殊样式</span><br><span class="line">::before       //在元素之前添加内容</span><br><span class="line">::after        //在元素之后添加内容</span><br><span class="line">::selection    //在被选取的部分添加样式</span><br></pre></td></tr></tbody></table></figure>
<p>before,after详细用法 [Mark:<a href="//blog.dimpurr.com/css-before-after/" target="_blank">//blog.dimpurr.com/css-before-after/</a>]</p>
<h2 id="Chrome下滚动条相关伪元素和伪类"><a href="#Chrome下滚动条相关伪元素和伪类" class="headerlink" title="Chrome下滚动条相关伪元素和伪类"></a>Chrome下滚动条相关伪元素和伪类</h2><p>伪元素</p>
<ul><li><span style="color: #ff0000;">::-webkit-scrollbar</span> 滚动条整体部分</li><li><span style="color: #ff0000;">::-webkit-scrollbar-thumb</span> 滚动条里面的小方块</li><li><span style="color: #ff0000;">::-webkit-scrollbar-track</span> 滚动条的外轨道,包含内轨道</li><li><span style="color: #ff0000;">::-webkit-scrollbar-button</span> 滚动条的轨道的两端按钮，允许通过点击微调小方块的位置。</li><li><span style="color: #ff0000;">::-webkit-scrollbar-track-piece</span> 内层轨道，滚动条中间部分,包含滑块</li><li><span style="color: #ff0000;">::-webkit-scrollbar-corner</span> 边角 即两个滚动条的交汇处</li><li><span style="color: #ff0000;">::-webkit-resizer&nbsp;</span>两个滚动条的交汇处上用于通过拖动调整元素大小的小控件</li>
</ul>
伪类
<ul><li><span style="color: #ff0000;">:horizontal</span> 水平滚动条</li><li><span style="color: #ff0000;">:vertical</span> 竖直滚动条</li><li><span style="color: #ff0000;">:decrement</span> 微调(向上或向左)按钮(小箭头,自定义)</li><li><span style="color: #ff0000;">:increment</span> 微调(向下或向右)按钮</li></ul>
<p>一个自定义滚动条的CSS示例</p>
<figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*定义滚动条宽度(竖直)和高度(水平)*/</span></span><br><span class="line">::-webkit-scrollbar {</span><br><span class="line">    width: <span class="number">8px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">8px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*定义滚动条轨道 内阴影+圆角*/</span></span><br><span class="line">::-webkit-scrollbar-track {</span><br><span class="line">    -webkit-box-shadow: inset <span class="number">0</span> <span class="number">0</span> <span class="number">6px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.3</span>);</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#FFF</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*定义竖向的滑块 内阴影+圆角*/</span></span><br><span class="line">::-webkit-scrollbar-thumb:vertical {</span><br><span class="line">    border-radius: <span class="number">10px</span>;</span><br><span class="line">    -webkit-<span class="attribute">box-shadow</span>: inset <span class="number">0</span> <span class="number">0</span> <span class="number">6px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,.<span class="number">3</span>);</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#CCC</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="jQuery大法实现多浏览器兼容的酷炫滚动条"><a href="#jQuery大法实现多浏览器兼容的酷炫滚动条" class="headerlink" title="jQuery大法实现多浏览器兼容的酷炫滚动条"></a>jQuery大法实现多浏览器兼容的酷炫滚动条</h4><p>有很多提供浏览器兼容的scrollbar插件,比如:<a target="_blank" href="https://github.com/noraesae/perfect-scrollbar.git">perfect-scrollbar插件</a>, 其使用方式如下</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//#container必须是position:relative;height有固定值的块级元素</span></span><br><span class="line"><span class="comment">//初始化</span></span><br><span class="line">$(<span class="string">'#container'</span>).perfectScrollbar();</span><br><span class="line">$(<span class="string">'#container'</span>).perfectScrollbar({</span><br><span class="line">    <span class="attr">wheelSpeed</span>: <span class="number">2</span>,</span><br><span class="line">    <span class="attr">wheelPropagation</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">minScrollbarLength</span>: <span class="number">20</span></span><br><span class="line">});</span><br><span class="line"><span class="comment">//更新滚动条</span></span><br><span class="line">$(<span class="string">'#container'</span>).perfectScrollbar(<span class="string">'update'</span>);</span><br><span class="line"><span class="comment">//删除滚动条</span></span><br><span class="line">$(<span class="string">'#container'</span>).perfectScrollbar(<span class="string">'destroy'</span>); <span class="comment">// Destroy</span></span><br><span class="line"><span class="comment">//事件</span></span><br><span class="line">$(<span class="string">'#container'</span>).on(<span class="string">"ps-scroll-[x|y|up|down|left|right]"</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{});</span><br><span class="line">$(<span class="string">'#container'</span>).on(<span class="string">"ps-[x|y]-reach-[start|end]"</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{});</span><br></pre></td></tr></tbody></table></figure>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[单例模式学习笔记]]></title>
            <link>https://code2life.top/blog/0004-singleton-pattern</link>
            <guid>https://code2life.top/blog/0004-singleton-pattern</guid>
            <pubDate>Mon, 23 Oct 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="单例模式学习笔记" tabindex="-1">单例模式学习笔记 <a class="header-anchor" href="#单例模式学习笔记" aria-label="Permalink to &quot;单例模式学习笔记&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="单例模式简介" tabindex="-1">单例模式简介 <a class="header-anchor" href="#单例模式简介" aria-label="Permalink to &quot;单例模式简介&quot;">&ZeroWidthSpace;</a></h3>
<h5 id="point-1-单例模式的作用" tabindex="-1">Point 1.单例模式的作用 <a class="header-anchor" href="#point-1-单例模式的作用" aria-label="Permalink to &quot;Point 1.单例模式的作用&quot;">&ZeroWidthSpace;</a></h5>
<ol>
<li>节省不必要的内存开销</li>
<li>屏蔽对象创建的复杂性</li>
<li>避免了类在外部被实例化</li>
</ol>
<h5 id="point-2-单例模式的特性" tabindex="-1">Point 2.单例模式的特性 <a class="header-anchor" href="#point-2-单例模式的特性" aria-label="Permalink to &quot;Point 2.单例模式的特性&quot;">&ZeroWidthSpace;</a></h5>
<ol>
<li>单例类只能有一个实例</li>
<li>单例类必须自己创建自己的唯一实例</li>
<li>单例类必须给所有其他对象提供这一实例</li>
</ol>
<h5 id="point-3-单例模式使用场景" tabindex="-1">Point 3.单例模式使用场景 <a class="header-anchor" href="#point-3-单例模式使用场景" aria-label="Permalink to &quot;Point 3.单例模式使用场景&quot;">&ZeroWidthSpace;</a></h5>
<p>只有一个实例的对象或只需要一个实例的对象</p>
<h3 id="静态语言中的单例模式" tabindex="-1">静态语言中的单例模式 <a class="header-anchor" href="#静态语言中的单例模式" aria-label="Permalink to &quot;静态语言中的单例模式&quot;">&ZeroWidthSpace;</a></h3>
<p>静态类型语言需要定义单例类,封装构造方法,并提供返回单例对象接口</p>
<h5 id="_1-懒加载形式-需要额外去实现线程安全" tabindex="-1">1.懒加载形式(需要额外去实现线程安全) <a class="header-anchor" href="#_1-懒加载形式-需要额外去实现线程安全" aria-label="Permalink to &quot;1.懒加载形式(需要额外去实现线程安全)&quot;">&ZeroWidthSpace;</a></h5>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton single</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (single </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            single </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> single;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //线程安全的, 但每次调用都会synchronized这个方法, 有额外性能损耗</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> synchronized</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInstanceSafe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (single </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)         </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            single </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> single;              </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><h5 id="_2-直接实例化形式-线程安全" tabindex="-1">2.直接实例化形式(线程安全) <a class="header-anchor" href="#_2-直接实例化形式-线程安全" aria-label="Permalink to &quot;2.直接实例化形式(线程安全)&quot;">&ZeroWidthSpace;</a></h5>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    private</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> final</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton1 single </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton1 </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> single;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h5 id="_3-双重校验形式-基本线程安全" tabindex="-1">3.双重校验形式(基本线程安全) <a class="header-anchor" href="#_3-双重校验形式-基本线程安全" aria-label="Permalink to &quot;3.双重校验形式(基本线程安全)&quot;">&ZeroWidthSpace;</a></h5>
<div class="language-java vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//只有第一次调用会执行同步, 无额外性能损耗</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> static</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Singleton </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (instance </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        synchronized</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Singleton.class) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (instance </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                instance </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Singleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> instance;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p><a href="//blog.csdn.net/kufeiyun/article/details/6166673" target="_blank" rel="noreferrer">产生线程不安全的原因, 看完后如提壶灌顶</a></p>
<h3 id="动态语言中的单例模式" tabindex="-1">动态语言中的单例模式 <a class="header-anchor" href="#动态语言中的单例模式" aria-label="Permalink to &quot;动态语言中的单例模式&quot;">&ZeroWidthSpace;</a></h3>
<blockquote>
<p>JS中无需创建单独的单例&quot;类&quot;的概念,函数作为一等对象,利用高阶函数可以实现抽象的单例工厂</p>
</blockquote>
<ol>
<li>全局变量 &nbsp;JS中可以使用带命名空间的全局变量&quot;实现&quot;单例模式</li>
<li>闭包实现的单例工厂(<span class="turn-red">重点</span>)</li>
</ol>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 惰性单例工厂:单例管理 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getSingleton</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">fn</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singleton </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singleton </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (singleton </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> fn.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">));</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 单例生成 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> A</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/*create object*/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> };</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 闭包中的singleton不会被回收,返回特定单例工厂 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singletonA </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getSingleton</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(singleObject);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 调用可获得特定单例 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singleInstance1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> singletonA</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singleInstance2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> singletonA</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(singleInstance1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> singleInstance2); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h3 id="summary" tabindex="-1">Summary <a class="header-anchor" href="#summary" aria-label="Permalink to &quot;Summary&quot;">&ZeroWidthSpace;</a></h3>
<ul>
<li>Java,C#等静态语言需要创建单例类实现,也可以利用反射机制创建单例,需要考虑线程安全问题.</li>
<li>js中也可以采用静态语言的思维创建单例类, 添加一个生成单例的函数获取单例</li>
<li>js闭包的特性提供了更加灵活和抽象的解决方案, 并且单线程的js无需考虑线程安全问题.</li>
</ul>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[前端Checkbox中的细节]]></title>
            <link>https://code2life.top/blog/0003-checkbox-detail</link>
            <guid>https://code2life.top/blog/0003-checkbox-detail</guid>
            <pubDate>Thu, 03 Aug 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="前端checkbox中的细节" tabindex="-1">前端Checkbox中的细节 <a class="header-anchor" href="#前端checkbox中的细节" aria-label="Permalink to &quot;前端Checkbox中的细节&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="选择器" tabindex="-1">选择器 <a class="header-anchor" href="#选择器" aria-label="Permalink to &quot;选择器&quot;">&ZeroWidthSpace;</a></h3>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// checkbox不同的选择方式   </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"[type=checkbox]"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"[type=checkbox]:checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox:not(:checked)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h3 id="判断选中" tabindex="-1">判断选中 <a class="header-anchor" href="#判断选中" aria-label="Permalink to &quot;判断选中&quot;">&ZeroWidthSpace;</a></h3>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 判断checkbox是否被选择   </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">prop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h3 id="改变状态" tabindex="-1">改变状态 <a class="header-anchor" href="#改变状态" aria-label="Permalink to &quot;改变状态&quot;">&ZeroWidthSpace;</a></h3>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// jQuery操作checkbox</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">prop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">click</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">trigger</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'click'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox:checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">removeAttr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'checked'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h3 id="小知识" tabindex="-1">小知识 <a class="header-anchor" href="#小知识" aria-label="Permalink to &quot;小知识&quot;">&ZeroWidthSpace;</a></h3>
<blockquote>
<p>jquery1.6版本便对attr()做出了修改,并增加了prop()</p>
</blockquote>
<p><strong>attr在某些情况下无效的原因</strong></p>
<ul>
<li>attr表示<span style="color: #ff0000;">Html</span>的attribute, 而prop表示<span style="color: #ff0000;">DOM</span>中的对象属性,即property;</li>
<li>attr的数据类型是<span style="color: #ff0000;">string</span>, 而prop可以表示boolean和object等<span style="color: #ff0000;">任何类型</span>;</li>
<li>prop的更新<span style="color: #ff0000;">一定</span>导致attr更新, attr更新<span style="color: #ff0000;">不一定</span>导致prop更新,如checked和value;</li>
<li>而attr在remove原型对象时会出错, 而prop会忽略这个错误</li>
</ul>
<p>关于attr和prop的详细对比<a href="//stackoverflow.com/questions/5874652/prop-vs-attr" target="_blank" rel="noreferrer">在这里有</a>
<span style="font-size: 18px;">所以</span>, 以下两种写法并<span style="color: #ff0000;">不一定</span>会获取或设置选中状态</p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">":checkbox"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"checked"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[[源码阅读] 60行代码的俄罗斯方块]]></title>
            <link>https://code2life.top/blog/0002-tatris</link>
            <guid>https://code2life.top/blog/0002-tatris</guid>
            <pubDate>Sat, 15 Jul 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="源码阅读-60行代码的俄罗斯方块" tabindex="-1">[源码阅读] 60行代码的俄罗斯方块 <a class="header-anchor" href="#源码阅读-60行代码的俄罗斯方块" aria-label="Permalink to &quot;[源码阅读] 60行代码的俄罗斯方块&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="原版" tabindex="-1">原版 <a class="header-anchor" href="#原版" aria-label="Permalink to &quot;原版&quot;">&ZeroWidthSpace;</a></h3>
<div class="language-html vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;!</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">doctype</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> html</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">html</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">head</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">head</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"box"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"width:252px;font:25px/25px 宋体;background:#000;color:#9f9;border:#999 20px ridge;text-shadow:2px 3px 1px #0f0;"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> map</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"["</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">23</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0x801,"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0xfff]"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tatris</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">[[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6600</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2222</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0xf00</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0xc600</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2640</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6c00</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4620</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4460</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2e0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6220</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x740</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2260</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0xe20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6440</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4700</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2620</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x720</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2320</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2700</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> keycom</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"38"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"rotate(1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"40"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"down()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"37"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"move(2,1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"39"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"move(0.5,-1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dia, pos, bak, run;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    dia</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">tatris[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~~</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    bak</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pos</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{fk:[],y:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,x:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,s:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~~</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)};</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    rotate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> over</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    document.onkeydown</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    clearInterval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(run);</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"GAME OVER"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    bak</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{fk:pos.fk.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),y:pos.y,x:pos.x,s:pos.s};</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(t) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,a2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">22</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        a2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">map[i].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"&#x3C;br/>"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,n; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">^</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bak.fk[i].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u25a1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            a2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">substr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,n</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bak.y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">15</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">RegExp.$_.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">RegExp.$1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(n</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">RegExp.$1.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getElementById</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"box"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).innerHTML</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u25a0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u3000</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((pos.fk[i]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">map[pos.y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i])</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">bak;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> rotate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">r</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> f</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dia[pos.s</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(pos.s</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">r)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">dia.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        pos.fk[i]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(f</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">>></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">12</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">15</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pos.x;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> down</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    ++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pos.y;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos.y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">22</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((map[pos.y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pos.fk[i])</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0xfff</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                map.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">splice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(pos.y</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">i,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), map.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">unshift</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x801</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(map[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x801</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> over</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> move</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">k</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    pos.x</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">k;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        pos.fk[i]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">t;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">onkeydown</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(keycom[(e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">event).keyCode]);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">run</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setInterval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"down()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">html</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br></div></div><h3 id="注释版" tabindex="-1">注释版 <a class="header-anchor" href="#注释版" aria-label="Permalink to &quot;注释版&quot;">&ZeroWidthSpace;</a></h3>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//eval的功能是把字符串变成实际运行时的JavaScript代码</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这里代码变换之后相当于 var map = [0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0x801, 0xfff];</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//其二进制形式如下</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001 十六进制对照 0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801            </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//100000000001              0x801</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//111111111111              0xfff</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//数据呈U形分布，没错，这就是俄罗斯方块的地图（或者游戏场地更为合适？）的存储区</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> map </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"["</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">23</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0x801,"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "0xfff]"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这个锯齿数组存储的是7种俄罗斯方块的图案信息</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//俄罗斯方块在不同的旋转角度下会产生不同的图案，当然可以通过算法实现旋转图案生成，这里为了减少代码复杂性直接给出了不同旋转状态下的图案数据</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//很明显，第一个0x6600就是7种俄罗斯方块之中的正方形方块</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0x6600二进制分四行表示如下</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0110</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0110</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0000</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//0000</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这就是正方形图案的表示，可以看出，这里统一采用一个16位数来存储4 * 4的俄罗斯方块图案</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//因为正方形图案旋转不会有形状的改变，所以此行只存储了一个图案数据</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tatris </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6600</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2222</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x0f00</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0xc600</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2640</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6c00</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4620</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4460</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2e0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6220</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x740</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2260</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x0e20</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x6440</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x4700</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">              [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2620</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x720</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2320</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x2700</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">]];  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//此对象之中存储的是按键键值（上，下，左，右）和函数之间的调用映射关系，之后通过eval可以做好调用映射</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> keycom </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"38"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"rotate(1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">              "40"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"down()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">              "37"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"move(2, 1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">              "39"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"move(0.5, -1)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//dia存储选取的俄罗斯方块类型（一共七种俄罗斯方块类型）</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//pos是前台正在下落的俄罗斯方块图案（每一种俄罗斯方块类型有属于自己的图案，如果不清楚可以查看上文的锯齿数组）对象</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//bak里存储关于pos图案对象的备份，在需要的时候可以实现对于pos运动的撤销</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dia, pos, bak, run;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//在游戏场景上方产生一个新的俄罗斯方块</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){ </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //产生0~6的随机数，~运算符在JavaScript依然是位取反运算，隐式实现了浮点数到整形的转换，这是一个很丑陋的取整实现方式</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //其作用是在七种基本俄罗斯方块类型之中随机选择一个</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    dia </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> tatris[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~~</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //pos和bak两个对象分别为前后台，实现俄罗斯方块运动的备份和撤销</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    bak </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {fk : [],                                    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这是一个数组存储的是图案转化之后的二进制数据</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                  y : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,                                        </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//初生俄罗斯方块的y坐标 </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                  x : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,                                     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//初生俄罗斯方块的x坐标，相对于右侧</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                  s : </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">~~</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">random</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dia.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)};        </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//在特定的俄罗斯方块类型之中随机选择一个具体的图案</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //新生的俄罗斯方块不旋转，所以这里参数为0</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    rotate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//旋转，实际上这里做的处理只不过是旋转旋转之后的俄罗斯方块具体图案，之后进行移位，根据X坐标把位置移动到场景里对应的地点</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> rotate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">r</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){ </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //这里是根据旋转参数 r 选择具体的俄罗斯方块图案，这里的 f ，就是上文之中的十六进制数</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //这里把当前pos.s的值和r（也就是旋转角度）相加，最后和dia.length求余，实现了旋转循环</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> f </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dia[pos.s </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (pos.s </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> r) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> dia.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //根据f（也就是上文之中提供的 16 位数据）每4位一行填写到fk数组之中</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //初生的俄罗斯方块pos.x的值为4，因为是4 * 4的团所以在宽度为12的场景里左移4位之后就位于中间四列范围内</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        pos.fk[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (f </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">>></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">12</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0x000f</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos.x;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //更新场景</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}      </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这是什么意思，这是一个判断，判断有没有重叠</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //对于当前俄罗斯方块图案进行逐行分析</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //把俄罗斯方块图案每一行的二进制位与场景内的二进制位进行位与，如果结果非0的话，那么这就证明图案和场景之中的实体（比如墙或者是已经落底的俄罗斯方块）重合了</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //既然重合了，那么之前的运动就是非法的，所以在这个if语句里面调用之前备份的bak实现对于pos的恢复</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((pos.fk[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> map[pos.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i]) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> bak;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }                            </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }    </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //如果没有重合，那么这里默认返回空</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}      </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//此函数产生用于绘制场景的字符串并且写入到div之中完成游戏场景的更新</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //把pos备份到bak之中，slice(0)意为从0号开始到结束的数组，也就是全数组，这里不能直接赋值，否则只是建立引用关系，起不到数据备份的效果</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    bak </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {fk : pos.fk.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">), y : pos.y, x : pos.x, s : pos.s};  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //如果俄罗斯方块和场景实体重合了的话，就直接return返回，不需要重绘场景</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (t) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //这里是根据map进行转换，转化得到的是01加上换行的原始串</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, a2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 22</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //br就是换行，在这个循环里，把地图之中所有数据以二进制数字的形式写入a2字符串</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //这里2是参数，指定基底，2的话就是返回二进制串的形式</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //slice(1, -1)这里的参数1，-1作用是取除了墙（收尾位）之外中间场景数据（10位）</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        a2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> map[i].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "&#x3C;br/>"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //这里实现的是对于字符串的替换处理，就是把原始的01字符串转换成为方块汉字串</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, n; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //这个循环处理的是正在下落的俄罗斯方块的绘制</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        ////\u25a1是空格方块，这里也是隐式使用正则表达式</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">[</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">^</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0]</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(bak.fk[i].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u25a1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))) { </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            a2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">substr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, n </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (bak.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 15</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> RegExp.$_.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> RegExp.$1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(n </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> RegExp.$1.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">length</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //对于a2字符串进行替换，并且显示在div之中，这里是应用</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    ////\u25a0是黑色方块 \u3000是空，这里实现的是替换div之中的文本，由数字替换成为两种方块或者空白</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getElementById</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"box"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).innerHTML </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a2.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">1</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u25a0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF">0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">g</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\u3000</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//游戏结束</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> over</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //撤销onkeydown的事件关联</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    document.onkeydown </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //清理之前设置的俄罗斯方块下落定时器</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    clearInterval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(run);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //弹出游戏结束对话框</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    alert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"游戏结束"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//俄罗斯方块下落</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> down</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){ </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //pos就是当前的（前台）俄罗斯方块，这里y坐标++，就相当于下落</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    ++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">pos.y; </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //如果俄罗斯方块和场景实体重合了的话</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()){ </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //这里的作用是消行</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 22</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            //和实体场景进行位或并且赋值，如果最后赋值结果为0xfff，也就说明当前行被完全填充了，可以消行</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((map[pos.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> pos.fk[i]) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0xfff</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                //行删除</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                map.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">splice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(pos.y </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">                //首行添加，unshift的作用是在数组第0号元素之前添加新元素，新的元素作为数组首元素</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                map.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">unshift</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0x801</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }                                </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //如果最上面一行不是空了，俄罗斯方块垒满了，则游戏结束</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(map[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0x801</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> over</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //这里重新产生下一个俄罗斯方块</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    } </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //否则的话更新，因为这里不是局部更新，是全局更新，所以重新绘制一下map就可以了</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//左右移动，t参数只能为2或者是0.5</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这样实现左移右移（相当于移位运算）这种方法也很丑陋，但是为了简短只能这样了</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这样做很丑陋，但是可以让代码简短一些</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> move</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">k</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">){  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    pos.x </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> k;  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; i</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        //*=t在这里实现了左右移1位赋值的功能</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        pos.fk[i] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">*=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> t;  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //左右移之后的更新，这里同样进行了重合判断，如果和左右墙重合的话，那么一样会撤销操作并且不更新场景</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    update</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">is</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//设置按键事件映射，这样按下键的时候就会触发对应的事件，具体来说就是触发对应的move，只有2和0.5</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">onkeydown</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">e</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {  </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    //eval生成的JavaScript代码，在这里就被执行了</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    eval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(keycom[(e </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> e </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> event).keyCode]);  </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  </span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//这样看来的话，这几乎是一个递归。。。</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">start</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//设置俄罗斯方块下落定时器，500毫秒触发一次，调节这里的数字可以调整游戏之中俄罗斯方块下落的快慢</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">run </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setInterval</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"down()"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">500</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br><span class="line-number">107</span><br><span class="line-number">108</span><br><span class="line-number">109</span><br><span class="line-number">110</span><br><span class="line-number">111</span><br><span class="line-number">112</span><br><span class="line-number">113</span><br><span class="line-number">114</span><br><span class="line-number">115</span><br><span class="line-number">116</span><br><span class="line-number">117</span><br><span class="line-number">118</span><br><span class="line-number">119</span><br><span class="line-number">120</span><br><span class="line-number">121</span><br><span class="line-number">122</span><br><span class="line-number">123</span><br><span class="line-number">124</span><br><span class="line-number">125</span><br><span class="line-number">126</span><br><span class="line-number">127</span><br><span class="line-number">128</span><br><span class="line-number">129</span><br><span class="line-number">130</span><br><span class="line-number">131</span><br><span class="line-number">132</span><br><span class="line-number">133</span><br><span class="line-number">134</span><br><span class="line-number">135</span><br><span class="line-number">136</span><br><span class="line-number">137</span><br><span class="line-number">138</span><br><span class="line-number">139</span><br><span class="line-number">140</span><br><span class="line-number">141</span><br><span class="line-number">142</span><br><span class="line-number">143</span><br><span class="line-number">144</span><br><span class="line-number">145</span><br><span class="line-number">146</span><br><span class="line-number">147</span><br><span class="line-number">148</span><br><span class="line-number">149</span><br><span class="line-number">150</span><br><span class="line-number">151</span><br><span class="line-number">152</span><br><span class="line-number">153</span><br><span class="line-number">154</span><br><span class="line-number">155</span><br><span class="line-number">156</span><br><span class="line-number">157</span><br><span class="line-number">158</span><br><span class="line-number">159</span><br><span class="line-number">160</span><br><span class="line-number">161</span><br><span class="line-number">162</span><br><span class="line-number">163</span><br><span class="line-number">164</span><br><span class="line-number">165</span><br><span class="line-number">166</span><br><span class="line-number">167</span><br><span class="line-number">168</span><br><span class="line-number">169</span><br><span class="line-number">170</span><br><span class="line-number">171</span><br><span class="line-number">172</span><br><span class="line-number">173</span><br><span class="line-number">174</span><br><span class="line-number">175</span><br><span class="line-number">176</span><br><span class="line-number">177</span><br><span class="line-number">178</span><br><span class="line-number">179</span><br><span class="line-number">180</span><br><span class="line-number">181</span><br><span class="line-number">182</span><br><span class="line-number">183</span><br><span class="line-number">184</span><br><span class="line-number">185</span><br><span class="line-number">186</span><br><span class="line-number">187</span><br><span class="line-number">188</span><br><span class="line-number">189</span><br><span class="line-number">190</span><br><span class="line-number">191</span><br><span class="line-number">192</span><br><span class="line-number">193</span><br><span class="line-number">194</span><br><span class="line-number">195</span><br><span class="line-number">196</span><br><span class="line-number">197</span><br><span class="line-number">198</span><br><span class="line-number">199</span><br><span class="line-number">200</span><br><span class="line-number">201</span><br><span class="line-number">202</span><br><span class="line-number">203</span><br><span class="line-number">204</span><br><span class="line-number">205</span><br><span class="line-number">206</span><br><span class="line-number">207</span><br><span class="line-number">208</span><br><span class="line-number">209</span><br><span class="line-number">210</span><br><span class="line-number">211</span><br><span class="line-number">212</span><br><span class="line-number">213</span><br><span class="line-number">214</span><br><span class="line-number">215</span><br><span class="line-number">216</span><br><span class="line-number">217</span><br><span class="line-number">218</span><br><span class="line-number">219</span><br><span class="line-number">220</span><br><span class="line-number">221</span><br><span class="line-number">222</span><br><span class="line-number">223</span><br><span class="line-number">224</span><br><span class="line-number">225</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[this call apply的用法总结]]></title>
            <link>https://code2life.top/blog/0001-js-note-call</link>
            <guid>https://code2life.top/blog/0001-js-note-call</guid>
            <pubDate>Tue, 27 Jun 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="this-call-apply的用法总结" tabindex="-1">this call apply的用法总结 <a class="header-anchor" href="#this-call-apply的用法总结" aria-label="Permalink to &quot;this call apply的用法总结&quot;">&ZeroWidthSpace;</a></h1>
<h3 id="this的特性与用法" tabindex="-1">this的特性与用法 <a class="header-anchor" href="#this的特性与用法" aria-label="Permalink to &quot;this的特性与用法&quot;">&ZeroWidthSpace;</a></h3>
<h5 id="作为对象的方法调用" tabindex="-1">作为对象的方法调用 <a class="header-anchor" href="#作为对象的方法调用" aria-label="Permalink to &quot;作为对象的方法调用&quot;">&ZeroWidthSpace;</a></h5>
<p><strong>指向该对象</strong></p>
<div class="language-javascript vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1处this指向对象a</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h5 id="作为普通函数调用" tabindex="-1">作为普通函数调用 <a class="header-anchor" href="#作为普通函数调用" aria-label="Permalink to &quot;作为普通函数调用&quot;">&ZeroWidthSpace;</a></h5>
<p><strong>指向全局对象</strong></p>
<div class="language-javascript vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "a"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">func</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//浏览器中this指向window对象; nodejs环境中指向global对象; 声明'use strict'指向undefined</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h5 id="构造器调用" tabindex="-1">构造器调用 <a class="header-anchor" href="#构造器调用" aria-label="Permalink to &quot;构造器调用&quot;">&ZeroWidthSpace;</a></h5>
<p><strong>指向构造器返回的对象</strong></p>
<div class="language-javascript vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1指向b对象</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h5 id="call或apply调用" tabindex="-1">call或apply调用 <a class="header-anchor" href="#call或apply调用" aria-label="Permalink to &quot;call或apply调用&quot;">&ZeroWidthSpace;</a></h5>
<p><strong>动态改变this</strong></p>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {};</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b,[]) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1指向b对象</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><h3 id="call-apply和bind" tabindex="-1">Call,Apply和Bind <a class="header-anchor" href="#call-apply和bind" aria-label="Permalink to &quot;Call,Apply和Bind&quot;">&ZeroWidthSpace;</a></h3>
<h5 id="定义、区别以及联系" tabindex="-1">定义、区别以及联系 <a class="header-anchor" href="#定义、区别以及联系" aria-label="Permalink to &quot;定义、区别以及联系&quot;">&ZeroWidthSpace;</a></h5>
<blockquote>
<p>bind()方法会创建一个新函数，称为绑定函数，当调用这个绑定函数时，绑定函数会以创建它时传入 bind()方法的第一个参数作为 this，
传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.</p>
</blockquote>
<p><strong>call/apply区别:call传入不定参数, apply传入(类)数组对象;</strong></p>
<ol>
<li>apply 、 call 、bind 三者都是用来<span style="color: #ff0000">改变函数的this对象的指向</span>的;</li>
<li>apply 、 call 、bind 三者第<span style="color: #ff0000">一个参数都是this要指向的对象</span>，也就是想指定的上下文;</li>
<li>apply 、 call 、bind 三者都可以利用<span style="color: #ff0000">后续参数传参</span>;</li>
<li>bind 是<span style="color: #ff0000">返回对应函数</span>，便于稍后调用；apply 、call 则是<span style="color: #ff0000">立即调用.</span></li>
</ol>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> obj </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    x: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}; </span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> foo </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    x: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    getX</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.x;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(foo.getX.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj)());  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1, 返回新函数(原函数+call/apply),不立即调用</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(foo.getX.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj));    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(foo.getX.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(obj));   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1  --call/apply/bind 运行时上下文[obj]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(foo.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getX</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">());            </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//2  --定义时上下文[foo]</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h5 id="常用用法" tabindex="-1">常用用法 <a class="header-anchor" href="#常用用法" aria-label="Permalink to &quot;常用用法&quot;">&ZeroWidthSpace;</a></h5>
<h6 id="改变参数类型" tabindex="-1">改变参数类型 <a class="header-anchor" href="#改变参数类型" aria-label="Permalink to &quot;改变参数类型&quot;">&ZeroWidthSpace;</a></h6>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> array1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> array1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.push.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(array1, array2); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//[1,2,3,4]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> numbers </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Math.max.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">apply</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(Math, numbers); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//3</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h6 id="类数组转换为标准数组" tabindex="-1">类数组转换为标准数组 <a class="header-anchor" href="#类数组转换为标准数组" aria-label="Permalink to &quot;类数组转换为标准数组&quot;">&ZeroWidthSpace;</a></h6>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> argumentsToArray</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.slice.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">arguments</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//转换后可以使用标准数组对象的所有方法</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h6 id="运行时绑定参数" tabindex="-1">运行时绑定参数 <a class="header-anchor" href="#运行时绑定参数" aria-label="Permalink to &quot;运行时绑定参数&quot;">&ZeroWidthSpace;</a></h6>
<div class="language-js vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> foo1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    bar : </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    eventBind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        $</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'click'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">            function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">event</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">                console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.bar); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            }.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">bind</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//click事件绑定了bind返回的新函数,运行时上下文是foo1对象</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">         );        </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div>]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
        <item>
            <title><![CDATA[Joey的书单]]></title>
            <link>https://code2life.top/blog/00001-reading-list</link>
            <guid>https://code2life.top/blog/00001-reading-list</guid>
            <pubDate>Sun, 01 Jan 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h1 id="joey的书单" tabindex="-1">Joey的书单 <a class="header-anchor" href="#joey的书单" aria-label="Permalink to &quot;Joey的书单&quot;">&ZeroWidthSpace;</a></h1>
<p>记录曾经读过的值得推荐的好书和一句话书评。</p>
<h2 id="技术类" tabindex="-1">技术类 <a class="header-anchor" href="#技术类" aria-label="Permalink to &quot;技术类&quot;">&ZeroWidthSpace;</a></h2>
<h5 id="《代码大全》——steve-mcconnell-⭐⭐⭐" tabindex="-1">《代码大全》——Steve McConnell ⭐⭐⭐ <a class="header-anchor" href="#《代码大全》——steve-mcconnell-⭐⭐⭐" aria-label="Permalink to &quot;《代码大全》——Steve McConnell ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>内容比较多，不有趣但有用。</p>
<h5 id="《代码整洁之道》——robert-c-martin-⭐⭐⭐⭐" tabindex="-1">《代码整洁之道》——Robert C.Martin ⭐⭐⭐⭐ <a class="header-anchor" href="#《代码整洁之道》——robert-c-martin-⭐⭐⭐⭐" aria-label="Permalink to &quot;《代码整洁之道》——Robert C.Martin ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>很有用，对写高质量高可维护性的代码很有帮助。</p>
<h5 id="《深入浅出java》——kathy-sierra-bert-bates-⭐⭐⭐⭐" tabindex="-1">《深入浅出Java》——Kathy Sierra，Bert Bates ⭐⭐⭐⭐ <a class="header-anchor" href="#《深入浅出java》——kathy-sierra-bert-bates-⭐⭐⭐⭐" aria-label="Permalink to &quot;《深入浅出Java》——Kathy Sierra，Bert Bates ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>大学刚学Java读的，很适合初学者。</p>
<h5 id="《码出高效-java开发手册》——阿里巴巴工程师团队-⭐⭐⭐⭐" tabindex="-1">《码出高效：Java开发手册》——阿里巴巴工程师团队 ⭐⭐⭐⭐ <a class="header-anchor" href="#《码出高效-java开发手册》——阿里巴巴工程师团队-⭐⭐⭐⭐" aria-label="Permalink to &quot;《码出高效：Java开发手册》——阿里巴巴工程师团队 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>精炼且实用，但个人感觉有些代码规则过严了。</p>
<h5 id="《sre-google运维解密》——google-sre团队-⭐⭐⭐⭐⭐" tabindex="-1">《SRE:Google运维解密》——Google SRE团队 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《sre-google运维解密》——google-sre团队-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《SRE:Google运维解密》——Google SRE团队 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列！必读经典，读后感：<a href="/blog/0041-goole-sre-thinking.html">/0041-goole-sre-thinking</a></p>
<h5 id="《深入浅出node-js》——朴灵-⭐⭐⭐⭐⭐" tabindex="-1">《深入浅出Node.js》——朴灵 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《深入浅出node-js》——朴灵-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《深入浅出Node.js》——朴灵 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>神作，由浅入深，把Node.js讲的非常清楚。</p>
<h5 id="《深入浅出rxjs》——程墨-⭐⭐⭐⭐" tabindex="-1">《深入浅出RxJs》——程墨 ⭐⭐⭐⭐ <a class="header-anchor" href="#《深入浅出rxjs》——程墨-⭐⭐⭐⭐" aria-label="Permalink to &quot;《深入浅出RxJs》——程墨 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>好看、有趣、有用，但需要一点函数响应式编程的基础才能理解。</p>
<h5 id="《javascript设计模式和实践》——曾探-⭐⭐⭐⭐⭐" tabindex="-1">《Javascript设计模式和实践》——曾探 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《javascript设计模式和实践》——曾探-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《Javascript设计模式和实践》——曾探 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>腾讯前端专家的神作，有趣又极其实用！需要有一些设计模式的基础和经验。</p>
<h5 id="《数学之美》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《数学之美》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《数学之美》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《数学之美》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列！吴军博士的每一本书都非常值得读。这本书与其他系列基本没有重合，非常生动地讲了数学在计算机科学中的应用，完完全全的干货，目前出到了第三版。</p>
<h5 id="《the-go-programming-language》——alan-a-a-donovan-·-brian-w-kernighan-go语言圣经-⭐⭐⭐⭐" tabindex="-1">《The Go Programming Language》——Alan A. A. Donovan · Brian W. Kernighan （Go语言圣经）⭐⭐⭐⭐ <a class="header-anchor" href="#《the-go-programming-language》——alan-a-a-donovan-·-brian-w-kernighan-go语言圣经-⭐⭐⭐⭐" aria-label="Permalink to &quot;《The Go Programming Language》——Alan A. A. Donovan · Brian W. Kernighan （Go语言圣经）⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>入门Golang必读，基础知识点都覆盖到了。</p>
<h5 id="《go-web编程》——郑兆雄-⭐⭐⭐" tabindex="-1">《Go Web编程》——郑兆雄 ⭐⭐⭐ <a class="header-anchor" href="#《go-web编程》——郑兆雄-⭐⭐⭐" aria-label="Permalink to &quot;《Go Web编程》——郑兆雄 ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>新加坡Golang专家的著作，说实话不太好看，但讲了不少实用的基础知识。</p>
<h5 id="《go-语言高级编程》——-柴树杉-曹春晖-⭐⭐⭐" tabindex="-1">《Go 语言高级编程》—— 柴树杉，曹春晖 ⭐⭐⭐ <a class="header-anchor" href="#《go-语言高级编程》——-柴树杉-曹春晖-⭐⭐⭐" aria-label="Permalink to &quot;《Go 语言高级编程》—— 柴树杉，曹春晖 ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p><a href="https://chai2010.cn/advanced-go-programming-book/" target="_blank" rel="noreferrer">https://chai2010.cn/advanced-go-programming-book/</a></p>
<p>还没看完系列。</p>
<h5 id="《深入理解linux内核》——-marco-cesati-daniel-p-bovet-⭐⭐⭐⭐" tabindex="-1">《深入理解Linux内核》—— Marco Cesati，Daniel P. Bovet ⭐⭐⭐⭐ <a class="header-anchor" href="#《深入理解linux内核》——-marco-cesati-daniel-p-bovet-⭐⭐⭐⭐" aria-label="Permalink to &quot;《深入理解Linux内核》—— Marco Cesati，Daniel P. Bovet ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>经典书籍，可惜我修为不够，一些内容看不懂，没有看完。</p>
<h5 id="《javascript高级程序设计》——nicholas-c-zakas⭐⭐⭐⭐" tabindex="-1">《JavaScript高级程序设计》——Nicholas C. Zakas⭐⭐⭐⭐ <a class="header-anchor" href="#《javascript高级程序设计》——nicholas-c-zakas⭐⭐⭐⭐" aria-label="Permalink to &quot;《JavaScript高级程序设计》——Nicholas C. Zakas⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>JS入门必读经典！内容非常全面，虽然有些内容有些过时了。</p>
<h5 id="《rust-程序设计语言》——steve-klabnik-carol-nichols-⭐⭐⭐" tabindex="-1">《Rust 程序设计语言》——Steve Klabnik，Carol Nichols ⭐⭐⭐ <a class="header-anchor" href="#《rust-程序设计语言》——steve-klabnik-carol-nichols-⭐⭐⭐" aria-label="Permalink to &quot;《Rust 程序设计语言》——Steve Klabnik，Carol Nichols ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p><a href="https://github.com/rust-lang/book" target="_blank" rel="noreferrer">https://github.com/rust-lang/book</a></p>
<p>可惜没读完系列，Rust的内存管理机制没搞懂，或许哪天用到Rust再回过头看吧。</p>
<h5 id="《人月神话》——-frederick-p-brooks-⭐⭐⭐⭐⭐" tabindex="-1">《人月神话》—— Frederick P.Brooks ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《人月神话》——-frederick-p-brooks-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《人月神话》—— Frederick P.Brooks ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列！软件工程必读经典。</p>
<h5 id="《分布式系统-概念与设计》——george-coulouris-jean-dollimore-tim-kindberg-gordon-blair-⭐⭐⭐⭐" tabindex="-1">《分布式系统：概念与设计》——George Coulouris，Jean Dollimore，Tim Kindberg，Gordon Blair ⭐⭐⭐⭐ <a class="header-anchor" href="#《分布式系统-概念与设计》——george-coulouris-jean-dollimore-tim-kindberg-gordon-blair-⭐⭐⭐⭐" aria-label="Permalink to &quot;《分布式系统：概念与设计》——George Coulouris，Jean Dollimore，Tim Kindberg，Gordon Blair ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>大学没选修分布式的课程，工作需要读了这本教材，理论性比较强，不太有趣，也没完全读完。</p>
<h5 id="《深入理解nginx》——陶辉-⭐⭐⭐" tabindex="-1">《深入理解Nginx》——陶辉 ⭐⭐⭐ <a class="header-anchor" href="#《深入理解nginx》——陶辉-⭐⭐⭐" aria-label="Permalink to &quot;《深入理解Nginx》——陶辉 ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>修为不够，可惜没读完系列。讲解了很多Nginx源码。</p>
<h5 id="《机器学习》——周志华-⭐⭐⭐⭐" tabindex="-1">《机器学习》——周志华 ⭐⭐⭐⭐ <a class="header-anchor" href="#《机器学习》——周志华-⭐⭐⭐⭐" aria-label="Permalink to &quot;《机器学习》——周志华 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>周志华教授的经典著作，又是修为不够，可惜没读完系列。</p>
<h5 id="《kubernetes指南》-https-kubernetes-feisky-xyz" tabindex="-1">《Kubernetes指南》 （<a href="https://kubernetes.feisky.xyz/%EF%BC%89" target="_blank" rel="noreferrer">https://kubernetes.feisky.xyz/）</a> <a class="header-anchor" href="#《kubernetes指南》-https-kubernetes-feisky-xyz" aria-label="Permalink to &quot;《Kubernetes指南》 （https://kubernetes.feisky.xyz/）&quot;">&ZeroWidthSpace;</a></h5>
<p>同名的电子书有好几个，大部分比较类似。除了上面这本，《Kubernetes Handbook》也还不错：<a href="https://jimmysong.io/kubernetes-handbook/" target="_blank" rel="noreferrer">https://jimmysong.io/kubernetes-handbook/</a>。读完这些指南书籍大概有个了解之后，再细品官方文档，受益颇多。</p>
<h5 id="《redis-深度历险-核心原理与应用实践》——钱文品-⭐⭐⭐⭐⭐" tabindex="-1">《Redis 深度历险：核心原理与应用实践》——钱文品 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《redis-深度历险-核心原理与应用实践》——钱文品-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《Redis 深度历险：核心原理与应用实践》——钱文品 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列！买的掘金小册电子书，最良心的一本小册，看了好多遍，每次都会有新的收获，非常佩服作者的功底。</p>
<h5 id="《美团点评技术年货》系列——美团工程师团队-⭐⭐⭐⭐" tabindex="-1">《美团点评技术年货》系列——美团工程师团队 ⭐⭐⭐⭐ <a class="header-anchor" href="#《美团点评技术年货》系列——美团工程师团队-⭐⭐⭐⭐" aria-label="Permalink to &quot;《美团点评技术年货》系列——美团工程师团队 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>似乎每年都会有这样一套技术文章的合集流出，浅显读过一些，感觉2018年左右的水平还是很高的，越往后反而干货更少了。</p>
<h5 id="《beyond-the-twelve-factor-app》——kevin-hoffman-⭐⭐⭐⭐" tabindex="-1">《Beyond the Twelve-Factor App》——Kevin Hoffman ⭐⭐⭐⭐ <a class="header-anchor" href="#《beyond-the-twelve-factor-app》——kevin-hoffman-⭐⭐⭐⭐" aria-label="Permalink to &quot;《Beyond the Twelve-Factor App》——Kevin Hoffman ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>没有中文译本，英语阅读有点吃力，好在只有57页。从实践中提炼出的理论，需要做一段时间后端开发之后，再看才能有共鸣。</p>
<h5 id="《贝叶斯思维-统计建模的python学习法》——allen-b-downey-⭐⭐⭐⭐" tabindex="-1">《贝叶斯思维：统计建模的Python学习法》——Allen B. Downey ⭐⭐⭐⭐ <a class="header-anchor" href="#《贝叶斯思维-统计建模的python学习法》——allen-b-downey-⭐⭐⭐⭐" aria-label="Permalink to &quot;《贝叶斯思维：统计建模的Python学习法》——Allen B. Downey ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>由浅入深，从贝叶斯定理出发，延伸出非常多的实际案例，讲解统计建模的方法论和思维方式。即假设先验概率，通过数据建模，得到似然函数，得到后验概率来估算、预测、决策等等，承认不确定性，以变化的视角看世界。可惜书中没有提到贝叶斯思维发展出的更高级的算法，比如贝叶斯滤波/卡尔曼滤波，贝叶斯信念网络，贝叶斯分类等等这些深刻地改变了社会的算法。</p>
<h5 id="《高性能javascript》-——nicholas-c-zakas-⭐⭐⭐" tabindex="-1">《高性能JavaScript》 ——Nicholas C. Zakas ⭐⭐⭐ <a class="header-anchor" href="#《高性能javascript》-——nicholas-c-zakas-⭐⭐⭐" aria-label="Permalink to &quot;《高性能JavaScript》 ——Nicholas C. Zakas ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>对提升JS水平有一定的帮助，但不少内容已经过时，尤其是大量关于优化IE浏览器的黑科技，如今IE已经退出了历史舞台，需要读者有辨别能力，汲取精华，略过已经不适用的内容。</p>
<h5 id="《effective-java》第三版-——joshua-bloch-⭐⭐⭐" tabindex="-1">《Effective Java》第三版 ——Joshua Bloch ⭐⭐⭐ <a class="header-anchor" href="#《effective-java》第三版-——joshua-bloch-⭐⭐⭐" aria-label="Permalink to &quot;《Effective Java》第三版 ——Joshua Bloch ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>比较经典的书，但是知识点略微零散，读起来不是特别顺畅。大部分案例是基于JDK自身的代码的，多数准则，相对于性能，作者其实更偏向于可维护性和可扩展性。</p>
<h5 id="《深入理解jvm虚拟机》-——周志明-⭐⭐⭐⭐⭐" tabindex="-1">《深入理解JVM虚拟机》 ——周志明 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《深入理解jvm虚拟机》-——周志明-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《深入理解JVM虚拟机》 ——周志明 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>名副其实的好书，对进阶学习Java甚至其他语言都有很大帮助。内容的连贯性和易读性很强，深入浅出，并不晦涩难懂。</p>
<h5 id="《深度学习》" tabindex="-1">《深度学习》 --- <a class="header-anchor" href="#《深度学习》" aria-label="Permalink to &quot;《深度学习》 ---&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《slo与sli-软件可靠性实践指南》" tabindex="-1">《SLO与SLI：软件可靠性实践指南》 --- <a class="header-anchor" href="#《slo与sli-软件可靠性实践指南》" aria-label="Permalink to &quot;《SLO与SLI：软件可靠性实践指南》 ---&quot;">&ZeroWidthSpace;</a></h5>
<h2 id="人文社科-自然科学-经管类" tabindex="-1">人文社科/自然科学/经管类 <a class="header-anchor" href="#人文社科-自然科学-经管类" aria-label="Permalink to &quot;人文社科/自然科学/经管类&quot;">&ZeroWidthSpace;</a></h2>
<h5 id="《浪潮之巅》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《浪潮之巅》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《浪潮之巅》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《浪潮之巅》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>吴军博士的神作！读书时仿佛穿越了时空到了上个世纪的硅谷，见证一次次技术革命的浪潮。目前已经出到了第四版。</p>
<h5 id="《智能时代》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《智能时代》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《智能时代》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《智能时代》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>好看且有用，前一部分与《全球科技通史》有一些重合，后一部分讲解大数据与人工智能等等前沿领域，外行也能理解。</p>
<h5 id="《全球科技通史》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《全球科技通史》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《全球科技通史》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《全球科技通史》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>从能量和信息的角度看历史，非常有趣。</p>
<h5 id="《见识》《态度》《格局》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《见识》《态度》《格局》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《见识》《态度》《格局》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《见识》《态度》《格局》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列，鸡汤三连！讲的其实是吴军博士自己的三观，可以学到优秀的人做人做事、教育孩子的方法。</p>
<h5 id="《文明之光》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《文明之光》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《文明之光》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《文明之光》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>非常好看，历史的正确打开方式！相比于用科学创造文明，彻底改变世界的伟人，功名赫赫的王侯将相显得黯淡无光。人们更应该铭记科罗廖夫和冯·布劳恩以及那些默默无闻的科学家和工程师，而不仅是飞天登月的加加林和阿姆斯特朗。</p>
<h5 id="《硅谷之谜》——吴军-⭐⭐⭐⭐⭐" tabindex="-1">《硅谷之谜》——吴军 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《硅谷之谜》——吴军-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《硅谷之谜》——吴军 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>相当于《浪潮之巅》的续集，其主旨和内容已经融入到第四版的《浪潮之巅》了。我在硅谷公司的中国研发中心任职，对吴军博士对硅谷的分析感触极深：叛逆和包容、多元文化、契约式合作和扁平化管理等等因素，以及背后控制论、信息论、系统论的指导理论，才是硅谷成功的真正奥秘。</p>
<h5 id="《人性的弱点》——dale-carnegie-⭐⭐⭐⭐⭐" tabindex="-1">《人性的弱点》——Dale Carnegie ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《人性的弱点》——dale-carnegie-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《人性的弱点》——Dale Carnegie ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>醍醐灌顶系列！成年以后读一读，对人性和社会的理解会更深。</p>
<h5 id="《人性的优点》——dale-carnegie-⭐⭐⭐" tabindex="-1">《人性的优点》——Dale Carnegie ⭐⭐⭐ <a class="header-anchor" href="#《人性的优点》——dale-carnegie-⭐⭐⭐" aria-label="Permalink to &quot;《人性的优点》——Dale Carnegie ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>没有《人性的弱点》好看，鸡汤味比较浓，虽然比不上吴军博士的鸡汤书，也算不错的了。</p>
<h5 id="《非暴力沟通》——marshall-b-rosenberg-⭐⭐⭐⭐" tabindex="-1">《非暴力沟通》——Marshall B.Rosenberg ⭐⭐⭐⭐ <a class="header-anchor" href="#《非暴力沟通》——marshall-b-rosenberg-⭐⭐⭐⭐" aria-label="Permalink to &quot;《非暴力沟通》——Marshall B.Rosenberg ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>非常有用的一本书，通篇都在说一个事情，长颈鹿沟通法。铭记：观察、感受、需要、请求四点，切勿直接对人发表评价。但实际上要做到这四点挺难的（比如现在我就在对一些书直接发表评价，这是典型的错误做法）。</p>
<h5 id="《一本小小的红色写作书》——brandon-royal-⭐⭐⭐⭐" tabindex="-1">《一本小小的红色写作书》——Brandon Royal ⭐⭐⭐⭐ <a class="header-anchor" href="#《一本小小的红色写作书》——brandon-royal-⭐⭐⭐⭐" aria-label="Permalink to &quot;《一本小小的红色写作书》——Brandon Royal ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>很实用，不过中文写作时有个别点不太适合，也不适合文学创作，非常适合工作中的写作。</p>
<h5 id="《一本小小的蓝色逻辑书》——brandon-royal-⭐⭐⭐⭐" tabindex="-1">《一本小小的蓝色逻辑书》——Brandon Royal ⭐⭐⭐⭐ <a class="header-anchor" href="#《一本小小的蓝色逻辑书》——brandon-royal-⭐⭐⭐⭐" aria-label="Permalink to &quot;《一本小小的蓝色逻辑书》——Brandon Royal ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>论点=论据+关键假设。书中有非常多的例子和练习，说明逻辑推理中的各种原则和陷阱，看完颇有收获。</p>
<h5 id="《南北战争300年》——李硕-⭐⭐⭐" tabindex="-1">《南北战争300年》——李硕 ⭐⭐⭐ <a class="header-anchor" href="#《南北战争300年》——李硕-⭐⭐⭐" aria-label="Permalink to &quot;《南北战争300年》——李硕 ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>史料翔实，主要讲冷兵器时代的战术战略，需要一些历史背景知识才能看懂，我看了一遍云里雾里的，还是没搞清楚三国两晋，南边的宋齐梁陈，北边的五胡十六国到底是怎么更迭的。</p>
<h5 id="《从1到无穷大》——george-gamow-⭐⭐⭐⭐" tabindex="-1">《从1到无穷大》——George Gamow ⭐⭐⭐⭐ <a class="header-anchor" href="#《从1到无穷大》——george-gamow-⭐⭐⭐⭐" aria-label="Permalink to &quot;《从1到无穷大》——George Gamow ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>非常好的经典科普作品，主要是数学和物理学方面的，需要一定的基础才能看懂。</p>
<h5 id="《时间简史》——stephen-hawking-⭐⭐⭐⭐" tabindex="-1">《时间简史》——Stephen Hawking ⭐⭐⭐⭐ <a class="header-anchor" href="#《时间简史》——stephen-hawking-⭐⭐⭐⭐" aria-label="Permalink to &quot;《时间简史》——Stephen Hawking ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>霍金的经典科普著作，有些内容感觉像在宇宙方面的纪录片中会看到的，宇宙的奥妙无穷啊。</p>
<h5 id="《富爸爸穷爸爸》——robert-kiyosaki-⭐⭐⭐" tabindex="-1">《富爸爸穷爸爸》——Robert Kiyosaki ⭐⭐⭐ <a class="header-anchor" href="#《富爸爸穷爸爸》——robert-kiyosaki-⭐⭐⭐" aria-label="Permalink to &quot;《富爸爸穷爸爸》——Robert Kiyosaki ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>妻子凑单买的畅销书，花一天半时间看完的，挺有趣。全书其实并没有教如何赚钱，而是告诉人们不要恐惧，不要贪婪，动脑筋去找潜在的机会。</p>
<h5 id="《货币战争3-金融高边疆》——宋鸿兵-⭐⭐⭐" tabindex="-1">《货币战争3：金融高边疆》——宋鸿兵 ⭐⭐⭐ <a class="header-anchor" href="#《货币战争3-金融高边疆》——宋鸿兵-⭐⭐⭐" aria-label="Permalink to &quot;《货币战争3：金融高边疆》——宋鸿兵 ⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>非常有意思的书，当小说看完的。至于其中传递的思想和观念，不完全苟同，从一个金融的维度去还原历史，是让人颇有启发的，但仅用金融的维度来诠释历史，甚至预测未来，就是对真实的世界复杂度和不确定性的降维处理了，保留自己的判断吧。</p>
<h5 id="《乌合之众》——gustave-le-bon-⭐⭐⭐⭐" tabindex="-1">《乌合之众》——Gustave Le Bon ⭐⭐⭐⭐ <a class="header-anchor" href="#《乌合之众》——gustave-le-bon-⭐⭐⭐⭐" aria-label="Permalink to &quot;《乌合之众》——Gustave Le Bon ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>个人感觉当代的情况相比作者写书的时代已经好很多了，书中描绘的群体特征仍然存在，但个体的盲从和狂热也转移到了其他领域，在群体中保持更多理性的人也比当年要多一些了。</p>
<h5 id="《相变》" tabindex="-1">《相变》 <a class="header-anchor" href="#《相变》" aria-label="Permalink to &quot;《相变》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《决断》" tabindex="-1">《决断》 <a class="header-anchor" href="#《决断》" aria-label="Permalink to &quot;《决断》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《从总账到总监》" tabindex="-1">《从总账到总监》 <a class="header-anchor" href="#《从总账到总监》" aria-label="Permalink to &quot;《从总账到总监》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《管理思维》" tabindex="-1">《管理思维》 <a class="header-anchor" href="#《管理思维》" aria-label="Permalink to &quot;《管理思维》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《资治通鉴》-——熊逸解读版" tabindex="-1">《资治通鉴》 ——熊逸解读版 <a class="header-anchor" href="#《资治通鉴》-——熊逸解读版" aria-label="Permalink to &quot;《资治通鉴》 ——熊逸解读版&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《于丹庄子心得》" tabindex="-1">《于丹庄子心得》 <a class="header-anchor" href="#《于丹庄子心得》" aria-label="Permalink to &quot;《于丹庄子心得》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《沉思录》" tabindex="-1">《沉思录》 <a class="header-anchor" href="#《沉思录》" aria-label="Permalink to &quot;《沉思录》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《必然》" tabindex="-1">《必然》 <a class="header-anchor" href="#《必然》" aria-label="Permalink to &quot;《必然》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《人类群星闪耀时》" tabindex="-1">《人类群星闪耀时》 <a class="header-anchor" href="#《人类群星闪耀时》" aria-label="Permalink to &quot;《人类群星闪耀时》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《the-culture-map》" tabindex="-1">《The Culture Map》 <a class="header-anchor" href="#《the-culture-map》" aria-label="Permalink to &quot;《The Culture Map》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《缩小差距》" tabindex="-1">《缩小差距》 <a class="header-anchor" href="#《缩小差距》" aria-label="Permalink to &quot;《缩小差距》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《你有你的计划-世界另有计划》" tabindex="-1">《你有你的计划，世界另有计划》 <a class="header-anchor" href="#《你有你的计划-世界另有计划》" aria-label="Permalink to &quot;《你有你的计划，世界另有计划》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《卓有成效的管理者》-彼得-德鲁克" tabindex="-1">《卓有成效的管理者》 彼得 德鲁克 <a class="header-anchor" href="#《卓有成效的管理者》-彼得-德鲁克" aria-label="Permalink to &quot;《卓有成效的管理者》 彼得 德鲁克&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《高效能人士的7个习惯》" tabindex="-1">《高效能人士的7个习惯》 <a class="header-anchor" href="#《高效能人士的7个习惯》" aria-label="Permalink to &quot;《高效能人士的7个习惯》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《回头客战略》" tabindex="-1">《回头客战略》 <a class="header-anchor" href="#《回头客战略》" aria-label="Permalink to &quot;《回头客战略》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《信任的速度》" tabindex="-1">《信任的速度》 <a class="header-anchor" href="#《信任的速度》" aria-label="Permalink to &quot;《信任的速度》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《创新思维与triz创新方法》" tabindex="-1">《创新思维与TRIZ创新方法》 <a class="header-anchor" href="#《创新思维与triz创新方法》" aria-label="Permalink to &quot;《创新思维与TRIZ创新方法》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《经济学通识》-薛兆丰" tabindex="-1">《经济学通识》 -- 薛兆丰 <a class="header-anchor" href="#《经济学通识》-薛兆丰" aria-label="Permalink to &quot;《经济学通识》 -- 薛兆丰&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《变量》-何帆" tabindex="-1">《变量》 -- 何帆 <a class="header-anchor" href="#《变量》-何帆" aria-label="Permalink to &quot;《变量》 -- 何帆&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《如何做成大事》-《how-big-things-get-done》" tabindex="-1">《如何做成大事》 《How big things get done》 <a class="header-anchor" href="#《如何做成大事》-《how-big-things-get-done》" aria-label="Permalink to &quot;《如何做成大事》 《How big things get done》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《经营力》——-稻盛和夫" tabindex="-1">《经营力》—— 稻盛和夫 <a class="header-anchor" href="#《经营力》——-稻盛和夫" aria-label="Permalink to &quot;《经营力》—— 稻盛和夫&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《李育辉组织行为学讲义》——-李育辉" tabindex="-1">《李育辉组织行为学讲义》—— 李育辉 <a class="header-anchor" href="#《李育辉组织行为学讲义》——-李育辉" aria-label="Permalink to &quot;《李育辉组织行为学讲义》—— 李育辉&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《中国历代政治得失》——-钱穆" tabindex="-1">《中国历代政治得失》—— 钱穆 <a class="header-anchor" href="#《中国历代政治得失》——-钱穆" aria-label="Permalink to &quot;《中国历代政治得失》—— 钱穆&quot;">&ZeroWidthSpace;</a></h5>
<h2 id="文学艺术类" tabindex="-1">文学艺术类 <a class="header-anchor" href="#文学艺术类" aria-label="Permalink to &quot;文学艺术类&quot;">&ZeroWidthSpace;</a></h2>
<h5 id="《围城》-——钱钟书-⭐⭐⭐⭐⭐" tabindex="-1">《围城》 ——钱钟书 ⭐⭐⭐⭐⭐ <a class="header-anchor" href="#《围城》-——钱钟书-⭐⭐⭐⭐⭐" aria-label="Permalink to &quot;《围城》 ——钱钟书 ⭐⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<p>读完《围城》，补习了一些知识才知道，钱钟书其实是一位大学者而不仅是作家。文字的魅力跃然纸上，读的是字，脑中是画，是剧，非常值得读的经典作品。戛然截止的结局留下太多回味，最后方鸿渐是否能突破自我的围城已经不重要，去悟这三座围城：婚姻，事业，自我。</p>
<h5 id="《银河帝国》——-艾萨克·阿西莫夫" tabindex="-1">《银河帝国》—— 艾萨克·阿西莫夫 <a class="header-anchor" href="#《银河帝国》——-艾萨克·阿西莫夫" aria-label="Permalink to &quot;《银河帝国》—— 艾萨克·阿西莫夫&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《球状闪电》——刘慈欣-⭐⭐⭐⭐" tabindex="-1">《球状闪电》——刘慈欣 ⭐⭐⭐⭐ <a class="header-anchor" href="#《球状闪电》——刘慈欣-⭐⭐⭐⭐" aria-label="Permalink to &quot;《球状闪电》——刘慈欣 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《边城》——沈从文-⭐⭐⭐⭐" tabindex="-1">《边城》——沈从文 ⭐⭐⭐⭐ <a class="header-anchor" href="#《边城》——沈从文-⭐⭐⭐⭐" aria-label="Permalink to &quot;《边城》——沈从文 ⭐⭐⭐⭐&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《瓦尔登湖》" tabindex="-1">《瓦尔登湖》 <a class="header-anchor" href="#《瓦尔登湖》" aria-label="Permalink to &quot;《瓦尔登湖》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《长安十二时辰》" tabindex="-1">《长安十二时辰》 <a class="header-anchor" href="#《长安十二时辰》" aria-label="Permalink to &quot;《长安十二时辰》&quot;">&ZeroWidthSpace;</a></h5>
<h5 id="《随风去野》" tabindex="-1">《随风去野》 <a class="header-anchor" href="#《随风去野》" aria-label="Permalink to &quot;《随风去野》&quot;">&ZeroWidthSpace;</a></h5>
]]></content:encoded>
            <author>code2life@ustc.edu (code2life)</author>
        </item>
    </channel>
</rss>