<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ackerr.</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.wzmmmmj.com/"/>
  <updated>2022-04-05T08:05:52.185Z</updated>
  <id>https://www.wzmmmmj.com/</id>
  
  <author>
    <name>ZmJ</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Golang中 println 与 fmt.Println 有什么区别</title>
    <link href="https://www.wzmmmmj.com/2021/07/06/println-and-Println/"/>
    <id>https://www.wzmmmmj.com/2021/07/06/println-and-Println/</id>
    <published>2021-07-06T23:46:49.000Z</published>
    <updated>2022-04-05T08:05:52.185Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在之前写的一个<a href="https://github.com/Ackerr/lab/" target="_blank" rel="noopener">命令行工具</a>，其中一个功能点是选择某个项目，并切换至该项目目录 （对应CLI的lab cs命令）。因为程序中不能直接修改shell当前路径，只好在命令执行后输出内容，再与shell内置命令搭配使用来实现。</p><p>伪代码如下：</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">path := GetPath()</span><br><span class="line">fmt.Println(path)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetPath</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="comment">// do something</span></span><br><span class="line"><span class="keyword">return</span> <span class="string">"/usr/local/bin/"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>正常情况下，执行<code>cd $(go run main.go)</code>后，shell路径会切换到<code>/usr/local/bin</code>。</p><p>但在一次修改中，年轻的我以为<code>println</code>与<code>fmt.Println</code>的输出是等价的，都是写入标准输出，但<code>println</code>是内置函数，不需要额外导入fmt包，就把项目中使用<code>fmt.Println</code>的地方都替换成<code>println</code>，从而<a href="https://github.com/Ackerr/lab/commit/a6611f05addc7a3d2caa168c9a90847bef249125#diff-e0f0eb674c3c8261dd55ff4e687ee203e54b8ccbc4d4678d3930b9fd4ea18d68R60" target="_blank" rel="noopener">埋下祸根</a>。</p><p>今早在本地更新了该命令行后，发现<code>lab cs</code>命令不好使了，虽然能在终端下看到该命令输出路径，但结合cd使用却没有效果，然而前一个版本，没问题的，间接定位到BUG是到改println的pr引入的。</p><h2 id="1-输出位置不同"><a href="#1-输出位置不同" class="headerlink" title="1.输出位置不同"></a>1.输出位置不同</h2><p>经紫月提醒，发现使用<code>cd $(lab cs 2&amp;&gt;1)</code>能正确切换路径了，那么原因很明确了，<code>println</code>把内容输出到标准错误中了。</p><p>查看<code>fmt.Println</code>的源码，注释和代码中都很明显：<strong>writes to standard output</strong>，内容输出到了os.Stdout，也就是标准输出。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Println formats using the default formats for its operands and writes to standard output.</span></span><br><span class="line"><span class="comment">// Spaces are always added between operands and a newline is appended.</span></span><br><span class="line"><span class="comment">// It returns the number of bytes written and any write error encountered.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Println</span><span class="params">(a ...<span class="keyword">interface</span>&#123;&#125;)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> Fprintln(os.Stdout, a...)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再来看看<code>println</code>，因为是内置函数，这里只写了函数说明，不过在注释中，可以找到这句：<strong>writes the result to standard error</strong>。说明<code>println/print</code>确实是输出到stderr，也就是标准错误输出。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// The println built-in function formats its arguments in an</span></span><br><span class="line"><span class="comment">// implementation-specific way and writes the result to standard error.</span></span><br><span class="line"><span class="comment">// Spaces are always added between arguments and a newline is appended.</span></span><br><span class="line"><span class="comment">// Println is useful for bootstrapping and debugging; it is not guaranteed</span></span><br><span class="line"><span class="comment">// to stay in the language.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">println</span><span class="params">(args ...Type)</span></span></span><br></pre></td></tr></table></figure><p>我们可以在go源码的 <code>runtime/print.go</code>找到<code>println</code>的具体实现。代码如下：</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printnl</span><span class="params">()</span></span> &#123;</span><br><span class="line">printstring(<span class="string">"\n"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printstring</span><span class="params">(s <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">gwrite(bytes(s))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// write to goroutine-local buffer if diverting output,</span></span><br><span class="line"><span class="comment">// or else standard error.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">gwrite</span><span class="params">(b []<span class="keyword">byte</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(b) == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">recordForPanic(b)</span><br><span class="line">gp := getg()</span><br><span class="line"><span class="comment">// Don't use the writebuf if gp.m is dying. We want anything</span></span><br><span class="line"><span class="comment">// written through gwrite to appear in the terminal rather</span></span><br><span class="line"><span class="comment">// than be written to in some buffer, if we're in a panicking state.</span></span><br><span class="line"><span class="comment">// Note that we can't just clear writebuf in the gp.m.dying case</span></span><br><span class="line"><span class="comment">// because a panic isn't allowed to have any write barriers.</span></span><br><span class="line"><span class="keyword">if</span> gp == <span class="literal">nil</span> || gp.writebuf == <span class="literal">nil</span> || gp.m.dying &gt; <span class="number">0</span> &#123;</span><br><span class="line">writeErr(b)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">n := <span class="built_in">copy</span>(gp.writebuf[<span class="built_in">len</span>(gp.writebuf):<span class="built_in">cap</span>(gp.writebuf)], b)</span><br><span class="line">gp.writebuf = gp.writebuf[:<span class="built_in">len</span>(gp.writebuf)+n]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>gwrite</code>函数中，这里主要看下<code>writeErr</code></p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">writeErr</span><span class="params">(b []<span class="keyword">byte</span>)</span></span> &#123;</span><br><span class="line">write(<span class="number">2</span>, unsafe.Pointer(&amp;b[<span class="number">0</span>]), <span class="keyword">int32</span>(<span class="built_in">len</span>(b)))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">write</span><span class="params">(fd <span class="keyword">uintptr</span>, p unsafe.Pointer, n <span class="keyword">int32</span>)</span> <span class="title">int32</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> write1(fd, p, n)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">write1</span><span class="params">(fd <span class="keyword">uintptr</span>, p unsafe.Pointer, n <span class="keyword">int32</span>)</span> <span class="title">int32</span></span></span><br></pre></td></tr></table></figure><p>可以看到writeErr传递了unitptr为2的fd参数，最终在write1处进行系统调用。而标准错误输出的文件描述符刚好是2。</p><blockquote><p>在unix中，stdin，stdout，stderr三者对应三个文件描述符，分别是0，1，2</p></blockquote><h2 id="2-函数定义不同"><a href="#2-函数定义不同" class="headerlink" title="2.函数定义不同"></a>2.函数定义不同</h2><p>平时都是直接使用<code>println(&quot;?&quot;)</code> 或 <code>fmt.Println(&quot;?&quot;)</code> ，不过两者的函数定义也有些不同。</p><figure class="highlight go"><table><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"><span class="comment">// println</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">println</span><span class="params">(args ...Type)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// fmt.Println</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Println</span><span class="params">(a ...<span class="keyword">interface</span>&#123;&#125;)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span></span><br></pre></td></tr></table></figure><p><strong>虽然两者都接受多个传参，但只有<code>fmt.Println</code>是具有返回值的</strong>，其中第一位返回输出内容的字节数，对于<code>Println</code>还需要加上末尾换行符<code>\n</code>的一字节。</p><p>除此之外，<code>println/print</code> 不接受数组和结构体参数。</p><h2 id="3-输出格式不同"><a href="#3-输出格式不同" class="headerlink" title="3.输出格式不同"></a>3.输出格式不同</h2><p>如果实参实现了<code>String()或Error()</code>方法时，在调用<code>fmt.Println</code>打印该参数时，会调用这两个方法，而内置的<code>println/print</code>则不会使用。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li><a href="https://github.com/golang/go/blob/master/src/runtime/print.go#L86" target="_blank" rel="noopener">https://github.com/golang/go/blob/master/src/runtime/print.go#L86</a></li><li><a href="https://gfw.go101.org/article/unofficial-faq.html#print-builtin-fmt-log" target="_blank" rel="noopener">https://gfw.go101.org/article/unofficial-faq.html#print-builtin-fmt-log</a></li><li><a href="https://www.zhihu.com/question/335186436" target="_blank" rel="noopener">https://www.zhihu.com/question/335186436</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;在之前写的一个&lt;a href=&quot;https://github.com/Ackerr/lab/&quot; target=&quot;_blank&quot; rel=&quot;n
      
    
    </summary>
    
    
    
      <category term="golang" scheme="https://www.wzmmmmj.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>深入浅出Golang之Context</title>
    <link href="https://www.wzmmmmj.com/2021/01/24/golang-context/"/>
    <id>https://www.wzmmmmj.com/2021/01/24/golang-context/</id>
    <published>2021-01-24T22:26:09.000Z</published>
    <updated>2022-04-05T08:05:52.183Z</updated>
    
    <content type="html"><![CDATA[<p>通过<code>context.Context</code> ，我们可以在 goruntine 中传递上下文参数以及同步取消信号。例如在处理http请求或记录请求链路时，可通过 Context 在goruntine间传递信息。也可以通过Context的超时取消，实现<a href="https://wzmmmmj.com/2020/10/31/golang-gracefully-upgrade/" target="_blank" rel="noopener">graceful shutdown</a>。本文从源码角度，分析<code>context.Context</code>是如何实现上下文传递以及同步取消信号。</p><blockquote><p>后遗症：具有网络请求的第三方库，如果无法传递Context，都不太想用，因为路径追踪中无法显示。</p></blockquote><h2 id="接口实现"><a href="#接口实现" class="headerlink" title="接口实现"></a>接口实现</h2><p><code>context.Context</code>接口</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Context <span class="keyword">interface</span> &#123;</span><br><span class="line">Deadline() (deadline time.Time, ok <span class="keyword">bool</span>)</span><br><span class="line">Done() &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">Err() error</span><br><span class="line">Value(key <span class="keyword">interface</span>&#123;&#125;) <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>Deadline：返回Context被取消的截止时间，如果第二个参数返回fasle，则说明未设置截止时间；</li><li>Done：返回一个channel对象，Context被取消后，该channel会被close；</li><li>Err：返回Context结束原因。当Done返回的channel未被取消时，返回nil，被取消则返回取消原因。例如Context因为超时关闭，返回<code>DeadlineExceeded</code>；</li><li>Value：返回Context中保存的键值；</li></ul><p><code>context.canceler</code>接口</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> canceler <span class="keyword">interface</span> &#123;</span><br><span class="line">cancel(removeFromParent <span class="keyword">bool</span>, err error)</span><br><span class="line">Done() &lt;-<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>cancel：取消当前Context;</li><li>Done：与Context接口一致，返回一个channel对象</li></ul><h2 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h2><p>context包中提供了两个常用方法，<code>context.Background</code>和<code>context.TODO</code>。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> (</span><br><span class="line">background = <span class="built_in">new</span>(emptyCtx)</span><br><span class="line">todo       = <span class="built_in">new</span>(emptyCtx)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Background</span><span class="params">()</span> <span class="title">Context</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> background</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TODO</span><span class="params">()</span> <span class="title">Context</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> todo</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从源码上看，两者并没什么不同，均通过<code>new(emptyCtx)</code>初始化。使用场景上，<code>context.Backgound</code>常用于主函数、初始化、以及测试用例中，作为顶层上下文进行传递，仅当不确定使用哪种Context时，才通过<code>context.TODO</code>占坑。对应emptyCtx实现如下：</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> emptyCtx <span class="keyword">int</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*emptyCtx)</span> <span class="title">Deadline</span><span class="params">()</span> <span class="params">(deadline time.Time, ok <span class="keyword">bool</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*emptyCtx)</span> <span class="title">Done</span><span class="params">()</span> &lt;-<span class="title">chan</span> <span class="title">struct</span></span>&#123;&#125; &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*emptyCtx)</span> <span class="title">Err</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(*emptyCtx)</span> <span class="title">Value</span><span class="params">(key <span class="keyword">interface</span>&#123;&#125;)</span> <span class="title">interface</span></span>&#123;&#125; &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不难发现，这两个 Context 啥都没干，没有截止时间，不能被取消，也不能设置上下文参数，仅仅通过空方法实现了Context接口。如果需要实现传递上下文、同步取消信号等额外功能，可通过context包中提供的方法进行拓展，当然也可以自己实现Context接口。</p><h2 id="WithValue"><a href="#WithValue" class="headerlink" title="WithValue"></a>WithValue</h2><p><code>context.WithValue</code>基于<code>parent Context</code>，生成valueCtx类型Context，并保留一对键值，常用来传递上下文。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">WithValue</span><span class="params">(parent Context, key, val <span class="keyword">interface</span>&#123;&#125;)</span> <span class="title">Context</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> parent == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"cannot create context from nil parent"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> key == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"nil key"</span>)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 因为传递的是interface，这里通过反射，判断key类型能否进行比较</span></span><br><span class="line"><span class="keyword">if</span> !reflectlite.TypeOf(key).Comparable() &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"key is not comparable"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;valueCtx&#123;parent, key, val&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> valueCtx <span class="keyword">struct</span> &#123;</span><br><span class="line">Context</span><br><span class="line">key, val <span class="keyword">interface</span>&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *valueCtx)</span> <span class="title">Value</span><span class="params">(key <span class="keyword">interface</span>&#123;&#125;)</span> <span class="title">interface</span></span>&#123;&#125; &#123;</span><br><span class="line"><span class="comment">// 比较key是否等于当前context key，如果不是则向父context查询</span></span><br><span class="line">    <span class="keyword">if</span> c.key == key &#123;</span><br><span class="line"><span class="keyword">return</span> c.val</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> c.Context.Value(key)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要注意的是，<code>valueCtx.Value</code>实现了链式查找，如果当前context中为找到为符合的key值，则会向父context中继续。</p><h3 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h3><p>示例代码中，ctx获取到grandparent context的value。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"context"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">ctx := context.Background()</span><br><span class="line">ctx = context.WithValue(ctx, <span class="string">"key1"</span>, <span class="string">"1"</span>)</span><br><span class="line">ctx = context.WithValue(ctx, <span class="string">"key2"</span>, <span class="string">"2"</span>)</span><br><span class="line">ctx = context.WithValue(ctx, <span class="string">"key3"</span>, <span class="string">"3"</span>)</span><br><span class="line"><span class="built_in">println</span>(ctx.Value(<span class="string">"key1"</span>).(<span class="keyword">string</span>))  <span class="comment">// "1"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WithCancel"><a href="#WithCancel" class="headerlink" title="WithCancel"></a>WithCancel</h2><p><code>context.WithCancel</code>常用于控制派生 goruntine，通过接收<code>parent Context</code>，返回两个参数<code>ctx cancelCtx</code>和<code>cancel cancelFunc</code>。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CancelFunc <span class="function"><span class="keyword">func</span><span class="params">()</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">WithCancel</span><span class="params">(parent Context)</span> <span class="params">(ctx Context, cancel CancelFunc)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> parent == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"cannot create context from nil parent"</span>)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 通过parent生成一个cancelCtx，会设置一个新的done channel对象</span></span><br><span class="line">c := newCancelCtx(parent)</span><br><span class="line">    <span class="comment">// 与parent关联</span></span><br><span class="line">propagateCancel(parent, &amp;c)</span><br><span class="line"><span class="keyword">return</span> &amp;c, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; c.cancel(<span class="literal">true</span>, Canceled) &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">newCancelCtx</span><span class="params">(parent Context)</span> <span class="title">cancelCtx</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> cancelCtx&#123;Context: parent&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> cancelCtx <span class="keyword">struct</span> &#123;</span><br><span class="line">Context</span><br><span class="line"></span><br><span class="line">mu       sync.Mutex</span><br><span class="line">done     <span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">children <span class="keyword">map</span>[canceler]<span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">err      error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里主要看下<code>propagateCancel</code>的实现。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// parent 父上下文</span></span><br><span class="line"><span class="comment">// child  当前上下文</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">propagateCancel</span><span class="params">(parent Context, child canceler)</span></span> &#123;</span><br><span class="line">done := parent.Done()</span><br><span class="line">    <span class="comment">// parent不会触发cancel，直接返回</span></span><br><span class="line">    <span class="comment">// 例如context.Background()这种Done方法直接返回nil的</span></span><br><span class="line"><span class="keyword">if</span> done == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="comment">// parent已经被cancel，child直接执行cancel</span></span><br><span class="line"><span class="keyword">case</span> &lt;-done:</span><br><span class="line">child.cancel(<span class="literal">false</span>, parent.Err())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 判断parent是否是cancelCtx类型</span></span><br><span class="line"><span class="keyword">if</span> p, ok := parentCancelCtx(parent); ok &#123;</span><br><span class="line">        <span class="comment">// parent是cancelCtx类型</span></span><br><span class="line">p.mu.Lock()</span><br><span class="line"><span class="keyword">if</span> p.err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 该ctx已经被cancel</span></span><br><span class="line">child.cancel(<span class="literal">false</span>, p.err)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 将当前context加入到上层cancelCtx的children中</span></span><br><span class="line"><span class="keyword">if</span> p.children == <span class="literal">nil</span> &#123;</span><br><span class="line">p.children = <span class="built_in">make</span>(<span class="keyword">map</span>[canceler]<span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">&#125;</span><br><span class="line">p.children[child] = <span class="keyword">struct</span>&#123;&#125;&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line">p.mu.Unlock()</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// parent不是cancelCtx类型，但可被cancel，监听parent和child是否被取消</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-parent.Done():</span><br><span class="line">child.cancel(<span class="literal">false</span>, parent.Err())</span><br><span class="line"><span class="keyword">case</span> &lt;-child.Done():</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过源码，<code>propagateCancel</code>函数主要作用就是关联当前context和父context，如果父context是cancelCtx类型并且未被cancel，则加入到它的children字段中。当执行 cancel 函数时，除了关闭自身<code>done channel</code>外，还会为 children 中关联的所有context执行 cancel 方法。通过Err方法，可获取<code>context.Canceled</code> 错误。<code>cancelCtx.cancel</code>实现如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Canceled = errors.New(<span class="string">"context canceled"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// removeFromParent 是否需要从parent.children中移除</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *cancelCtx)</span> <span class="title">cancel</span><span class="params">(removeFromParent <span class="keyword">bool</span>, err error)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"context: internal error: missing cancel error"</span>)</span><br><span class="line">&#125;</span><br><span class="line">c.mu.Lock()</span><br><span class="line">    <span class="comment">// err不为nil，说明已经被取消，直接返回</span></span><br><span class="line"><span class="keyword">if</span> c.err != <span class="literal">nil</span> &#123;</span><br><span class="line">c.mu.Unlock()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 设置err，并关闭done chan</span></span><br><span class="line">c.err = err</span><br><span class="line"><span class="keyword">if</span> c.done == <span class="literal">nil</span> &#123;</span><br><span class="line">c.done = closedchan</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="built_in">close</span>(c.done)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 关联的所有子context依次执行cancel</span></span><br><span class="line"><span class="keyword">for</span> child := <span class="keyword">range</span> c.children &#123;</span><br><span class="line">child.cancel(<span class="literal">false</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">c.children = <span class="literal">nil</span></span><br><span class="line">c.mu.Unlock()</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> removeFromParent &#123;</span><br><span class="line">        <span class="comment">// 在parent的children字段中移除该context</span></span><br><span class="line">removeChild(c.Context, c)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="举个例子-1"><a href="#举个例子-1" class="headerlink" title="举个例子"></a>举个例子</h3><p>示例代码，sleep 3秒后，因为执行 cancel，ctx.Err()输出<code>context canceled</code>。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">ctx := context.Background()</span><br><span class="line">ctx, cancel := context.WithCancel(ctx)</span><br><span class="line"><span class="keyword">go</span> f(ctx)</span><br><span class="line">time.Sleep(time.Second * <span class="number">3</span>)</span><br><span class="line">cancel()</span><br><span class="line">time.Sleep(time.Second * <span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">f</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line">i := <span class="number">1</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">fmt.Println(ctx.Err())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(i)</span><br><span class="line">time.Sleep(time.Second * <span class="number">1</span>)</span><br><span class="line">i++</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WithDeadline"><a href="#WithDeadline" class="headerlink" title="WithDeadline"></a>WithDeadline</h2><p><code>context.WithDeadline</code>常用于需要超时关闭的场景。通过设置截止时间，起到超时自动取消context以及子context的效果。当context因为超时被 cancel 时，通过Err方法，可获取<code>context.DeadlineExceeded</code> 错误，代码如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">WithDeadline</span><span class="params">(parent Context, d time.Time)</span> <span class="params">(Context, CancelFunc)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> parent == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">"cannot create context from nil parent"</span>)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 判断parent是否设置截止时间，并且是否达到该时间</span></span><br><span class="line"><span class="keyword">if</span> cur, ok := parent.Deadline(); ok &amp;&amp; cur.Before(d) &#123;</span><br><span class="line">        <span class="comment">// 如果d时间已过，则返回cancelCtx类型context，截止时间使用parent的</span></span><br><span class="line"><span class="keyword">return</span> WithCancel(parent)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 将父级context先包装成cancelCtx类型</span></span><br><span class="line">c := &amp;timerCtx&#123;</span><br><span class="line">cancelCtx: newCancelCtx(parent),</span><br><span class="line">deadline:  d,</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 关联parent</span></span><br><span class="line">propagateCancel(parent, c)</span><br><span class="line">    <span class="comment">// 如果当前时间已经超过了截止时间，执行cancel，直接返回一个已经被 cancel 的 timerCtx</span></span><br><span class="line">dur := time.Until(d)</span><br><span class="line"><span class="keyword">if</span> dur &lt;= <span class="number">0</span> &#123;</span><br><span class="line">c.cancel(<span class="literal">true</span>, DeadlineExceeded)</span><br><span class="line"><span class="keyword">return</span> c, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; c.cancel(<span class="literal">false</span>, Canceled) &#125;</span><br><span class="line">&#125;</span><br><span class="line">c.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> c.mu.Unlock()</span><br><span class="line"><span class="keyword">if</span> c.err == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="comment">// 启动一个定时器，到截止时间自动取消这个 timerCtx</span></span><br><span class="line">c.timer = time.AfterFunc(dur, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">c.cancel(<span class="literal">true</span>, DeadlineExceeded)</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> c, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; c.cancel(<span class="literal">true</span>, Canceled) &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="举个例子-2"><a href="#举个例子-2" class="headerlink" title="举个例子"></a>举个例子</h3><p>示例代码，执行3秒后，定时器执行 cancel，ctx.Err()输出<code>context deadline exceeded</code>。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">ctx := context.Background()</span><br><span class="line">    ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second * <span class="number">3</span>))</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line"><span class="keyword">go</span> f(ctx)</span><br><span class="line">time.Sleep(time.Second * <span class="number">5</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">f</span><span class="params">(ctx context.Context)</span></span> &#123;</span><br><span class="line">i := <span class="number">1</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">fmt.Println(ctx.Err())</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(i)</span><br><span class="line">time.Sleep(time.Second * <span class="number">1</span>)</span><br><span class="line">i++</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WithTimeout"><a href="#WithTimeout" class="headerlink" title="WithTimeout"></a>WithTimeout</h2><p>与<code>context.WithDeadline</code>函数类似，只不过传参从time.Time类型该为time.Duration，也就是截止时间改为超时时间。实际调用的其实还是<code>context.WithDeadline</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">WithTimeout</span><span class="params">(parent Context, timeout time.Duration)</span> <span class="params">(Context, CancelFunc)</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> WithDeadline(parent, time.Now().Add(timeout))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;通过&lt;code&gt;context.Context&lt;/code&gt; ，我们可以在 goruntine 中传递上下文参数以及同步取消信号。例如在处理http请求或记录请求链路时，可通过 Context 在goruntine间传递信息。也可以通过Context的超时取消，实现&lt;a h
      
    
    </summary>
    
    
    
      <category term="go" scheme="https://www.wzmmmmj.com/tags/go/"/>
    
  </entry>
  
  <entry>
    <title>装机实录之超频篇</title>
    <link href="https://www.wzmmmmj.com/2021/01/10/overclock/"/>
    <id>https://www.wzmmmmj.com/2021/01/10/overclock/</id>
    <published>2021-01-10T23:51:41.000Z</published>
    <updated>2022-04-05T08:05:52.185Z</updated>
    
    <content type="html"><![CDATA[<p>经历一天多次黑屏重启，初步完成了首次超频之旅。本文记录一下作为萌新超频的整个过程，就当图一乐，真正学技术还得看贴吧。</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>先介绍一下平台</p><ul><li>CPU：AMD Ryzen 5800x</li><li>主板：微星B550-M</li><li>显卡：微星RTX 3070</li><li>内存：芝奇幻光戟3200MHz c16 </li></ul><p>本篇文章中使用到的软件及在本文中的用途</p><ul><li><a href="https://www.aida64.com/downloads" target="_blank" rel="noopener">AIDA64</a>：硬件检测工具，对CPU进行压力测试，判断CPU稳定性</li><li><a href="https://www.cpuid.com/softwares/cpu-z.html" target="_blank" rel="noopener">CPU-Z</a>：侦察CPU信息，对CPU进行单核/多核评分和稳定性测试</li><li><a href="https://www.amd.com/zh-hans/technologies/ryzen-master" target="_blank" rel="noopener">AMD Ryzen Master</a>：AMD官方提供的锐龙系CPU超频工具</li><li><a href="https://www.techpowerup.com/download/gpu-z/" target="_blank" rel="noopener">GPU-Z</a>：侦察显卡信息</li><li><a href="https://benchmarks.ul.com/3dmark" target="_blank" rel="noopener">3DMark</a>：通过Time spy压力测试，判断显卡稳定性</li><li><a href="https://www.msi.com/Landing/afterburner" target="_blank" rel="noopener">Afterburner</a>：显卡超频工具，监控硬件图标</li><li><a href="https://www.guru3d.com/files-details/rtss-rivatuner-statistics-server-download.html" target="_blank" rel="noopener">RTSS</a>：显示Afterburner中监控的硬件指标</li></ul><h2 id="CPU超频"><a href="#CPU超频" class="headerlink" title="CPU超频"></a>CPU超频</h2><p>CPU下的超频需要主板和CPU同时支持，Intel这边需要型号末尾为K，Z系列主板，而AMD锐龙全系列支持，除部分A系列主板外。</p><p>通过CPU-Z查看默频下的cpu信息，顺便记一下默频跑分（单核661.5/多核6584.4），如下图所示</p><p><img src="/images/overclocking-1.png" alt="默频跑分"></p><p>大部分教程中，会通过BIOS修改参数进行超频，但此方法需要来回重启电脑进入BIOS，许多主板产商会提供相关的超频工具，这里我们用AMD官方的超频工具AMD Ryzen Master进行超频。</p><p>打开AMD Ryzen Master，面板中可看到当前CPU核心频率和电压以及它们的最大值，如下图</p><p><img src="/images/overclocking-2.png" alt="默频"></p><p>如果想要超频，只需调整上图右侧控制模式为手动，微调频率和电压即可。这里推荐核心频率每次增加200MHz，点击运行和测试，如果测试过程中出现死机或重启的情况，适当的上调电压，再重复上述过程，直达达到预期频率。至于CPU的最佳频率请自行搜索。如下图所示，CPU核心频率从默频3.8GHz提高到了4.6GHz，并通过测试。</p><p><img src="/images/overclocking-3.png" alt="超频后"></p><p>接着再通过CPU-Z测试一下当前频率的跑分（单核631.1/多核6681.0），对比默频下的跑分，发现虽然多核分数提高了，但单核分数反而降低了。这里可能是因为CPU默频，在高负载下会自动睿频到4.8Ghz，从而提升了单核性能。</p><p><img src="/images/overclocking-4.png" alt="超频后跑分"></p><p>通过Ryzen Master以及CPU-Z的粗略的压力测试后，再通过AIDA64中的进行稳定性测试，勾选Stress FPU，点击Start，观察CPU-Z中的频率以及电压看是否稳定，CPU温度是否在合理范围内。在稳定跑半个小时左右，基本宣告超频完成，如果途中出现黑屏重启或死机，适当调高电压，最好不超过1.45v，不然你懂得。如果希望进一步提升频率，重复上述步骤即可。</p><p><img src="/images/overclocking-5.png" alt="AIDA64"></p><p>由于Ryzen Master重启后需要手动启动才能生效，在确定CPU核心频率及电压后，只需要进一次BIOS，在对应的位置上设置好参数即可。</p><h2 id="显卡超频"><a href="#显卡超频" class="headerlink" title="显卡超频"></a>显卡超频</h2><p>显卡超频与CPU超频类似，也是通过调整核心频率，再执行对应的压力测试即可。</p><p>通过MSI Afterburner来提高核心频率，如下图所示，先将功耗限制以及温度限制拉满。</p><p><img src="/images/overclocking-6.png" alt="Afterburner"></p><p>按照每次提高50MHz核心频率，如果对显卡自信的话，也可以提高多一些。设置完成后点击右下角保存，接下来只需通过显卡压测软件进行压测即可。这里推荐 FurMark 或 3DMark的Time Spy压力测试，如果设置的频率未通过压测，就降低20MHz，如此反复，直到通过压测。</p><p><img src="/images/overclocking-7.png" alt="3DMark"></p><p>通过压测后，如需开机启动超频配置，只需要对相应的配置点击startup即可</p><p><img src="/images/overclocking-8.png" alt="startup"></p><p>另外，在许多评测视频中，经常能看到在游戏画面中显示硬件信息，就是通过MSI Afterburner的硬件实时监控功能，如果想在游戏中显示各类硬件信息，可在MSI Afterburner设置中，开启OSD显示。</p><p><img src="/images/overclocking-9.png" alt="Afterburner OSD"></p><p>开启OSD并启动RTSS后，即可在游戏中显示数据，如下图左上角。</p><p><img src="/images/overclocking-10.png" alt="OSD"></p><h2 id="内存超频"><a href="#内存超频" class="headerlink" title="内存超频"></a>内存超频</h2><p>由于CPU具有默认支持的内存频率，例如DDR4可能是2133MHz，如果购买了DDR4 3200MHz或更高，在未修改BIOS的前提下，开机内存频率只会是2133MHz。而达到3200MHz，则需要我们手动进入BIOS，开启XMP，也就是内存预设超频。</p><p><img src="/images/overclocking-11.png" alt="XMP设置"></p><p><img src="/images/overclocking-12.png" alt="开启XMP后的内存频率"></p><p>当然内存也支持手动超频，具体的超频能力实际跟购买内存颗粒有关。但在体验了一次扣主板电池后，我放弃了这个选项，不然不知道又要重置多少次BIOS，只能自我安慰，预设超频挺好的：）。具体的手动超频可参考<a href="https://post.smzdm.com/p/a25rz47n/" target="_blank" rel="noopener">这篇文章</a>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>相对于内存超频，会导致电脑无法开机，需要重置BIOS，CPU和显卡则安心的多，大不了就是黑屏重启。可能本身性能过剩的原因，也可能是操作不到位，超频并没有给我太多直观上的提升。当然喜欢折腾的人可以来试试看</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;经历一天多次黑屏重启，初步完成了首次超频之旅。本文记录一下作为萌新超频的整个过程，就当图一乐，真正学技术还得看贴吧。&lt;/p&gt;
&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;先介绍一
      
    
    </summary>
    
    
    
  </entry>
  
  <entry>
    <title>装机实录之组装篇</title>
    <link href="https://www.wzmmmmj.com/2020/12/13/diy-computer-install/"/>
    <id>https://www.wzmmmmj.com/2020/12/13/diy-computer-install/</id>
    <published>2020-12-13T23:45:06.000Z</published>
    <updated>2022-04-05T08:05:52.181Z</updated>
    
    <content type="html"><![CDATA[<p>本篇依旧是装机时个人觉得特别重要的几点注意事项。对于配件安装这里就不赘述，网上有很多装机视频，安装操作都有比较细致的讲解，B站搜索<a href="https://search.bilibili.com/all?keyword=%E8%A3%85%E6%9C%BA%E6%95%99%E7%A8%8B&order=stow&duration=0&tids_1=0" target="_blank" rel="noopener">装机教程</a>即可。</p><p>按照惯例，先展示包装（空壳）全家福以及机箱图</p><p><img src="/images/diy-setup-1.png" alt="包装图"></p><p><img src="/images/diy-setup-2.png" alt="开机机箱图"></p><blockquote><p>风扇没有RGB感觉还是差点意思</p></blockquote><h2 id="风道"><a href="#风道" class="headerlink" title="风道"></a>风道</h2><p>合理的风道设计，有利于机箱散热，不然容易使机箱变成闷罐，动不动就高温降频。对于风道设计，推荐遵循前进后出，下进上出。对于单风扇水冷，直接安装在主板左侧即可。因为我买的一体式240水冷，还需要考虑冷排放置，而我的机箱并不支持机箱顶部放置水冷，所以只能安装在机箱前面板。参考此篇<a href="https://zhuanlan.zhihu.com/p/151374071" target="_blank" rel="noopener">文章</a>，最终选择吹风式（冷排|风扇|前面板）。当然如何是RGB的风扇，机箱前面板不透光的情况，肯定选择抽风式，而如果有多余风扇则可以采取夹汉堡方式，在冷排两侧都安装风扇。</p><h2 id="接线"><a href="#接线" class="headerlink" title="接线"></a>接线</h2><p>装机时，推荐先接线，待点亮电脑后，再调整走线。正常情况下会有以下几种线材：</p><ol><li>电源供电线</li><li>机箱IO跳线</li><li>风扇fan线</li><li>硬盘SATA线</li></ol><p>各种线材的接线方式请反复观看<a href="https://www.bilibili.com/video/BV1jE411e7hw?p=11" target="_blank" rel="noopener">此视频</a>，配合主板说明书中的接口图效果更佳。小弟认全电源供电线就看了不下10遍，这里推荐购买全模组电源，对于多余的供电线，可以拆卸，方便后续理线。</p><h2 id="系统"><a href="#系统" class="headerlink" title="系统"></a>系统</h2><p>首先准备一个16gb的u盘，安装windows系统，最方便的方式就是在<a href="https://www.microsoft.com/zh-cn/software-download/windows10" target="_blank" rel="noopener">微软官方</a>下载iso文件到u盘中。如果有其他系统需求，也可以通过 <a href="https://rufus.ie/zh_CN.html" target="_blank" rel="noopener">Rufus</a> 制作系统盘，使用方式可参考<a href="https://www.zhihu.com/question/25400852" target="_blank" rel="noopener">知乎回答</a>。至于那些带有捆绑软件的系统安装工具能不碰就不碰。当然如果嫌麻烦，也可以问问身边朋友有没有可用的系统盘，或者购买固态硬盘时，顺便买一个系统盘或直接让商家帮忙安装好系统。</p><h2 id="BIOS"><a href="#BIOS" class="headerlink" title="BIOS"></a>BIOS</h2><p>安装好所有配件，接上电源，显示器后，插上系统u盘，启动开机键后，猛按F11 (不同主板的快捷键可能不同，请查看主板说明书或自己搜索)。正常情况会出现 <code>开机启动设备</code>界面，选择需要安装系统的硬盘即可。如果未出现设备，需要进入BIOS设置界面查看设置是否正确：</p><ul><li>BIOS Mode 设置为UEFI</li><li>SATA Mode 设置为 AHCI</li></ul><p>修改设置重启后，正常情况下就会进入安装系统的傻瓜界面了。</p><p><img src="/images/diy-setup-3.png" alt="BIOS"></p><p>小弟第一次到这步时，启动后长时间停留在logo界面，原本以为是正常现象，结果出门吃个饭回来还在logo界面。刚开始以为是固态问题，因为BIOS中主板没有识别出固态，如上图，截图给固态客服后，发现设置并没有什么问题。尝试取下固态再安装，折腾了半天还是识别不了，一度让我以为固态有问题，差点去退货了。而后又去问了主板客服，让我尝试<strong>升级主板BIOS</strong>，通过官网中下载新的主板BIOS版本至u盘中，并在BIOS中加载该版本。一顿操作之后，终于识别出固态。 </p><h2 id="理线"><a href="#理线" class="headerlink" title="理线"></a>理线</h2><p>此处灵魂走线大师上线。理线推荐购买扎线带和魔术贴，一般购买的电源中也会附赠。秉承着能看就行的原则，尝试性的理了一回线，只能说尽力了，反正背板一盖，啥都看不到（<del>自暴自弃</del>）。</p><p>理线前：</p><p><img src="/images/diy-setup-4.png" alt="理线前"></p><p>理线后：</p><p><img src="/images/diy-setup-5.png" alt="理线后"></p><p>最后附上一篇个人感觉还不错的<a href="https://zhuanlan.zhihu.com/p/129807679" target="_blank" rel="noopener">理线教程</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;本篇依旧是装机时个人觉得特别重要的几点注意事项。对于配件安装这里就不赘述，网上有很多装机视频，安装操作都有比较细致的讲解，B站搜索&lt;a href=&quot;https://search.bilibili.com/all?keyword=%E8%A3%85%E6%9C%BA%E6%9
      
    
    </summary>
    
    
    
      <category term="生活" scheme="https://www.wzmmmmj.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>常用依赖镜像源</title>
    <link href="https://www.wzmmmmj.com/2020/11/29/mirror-source/"/>
    <id>https://www.wzmmmmj.com/2020/11/29/mirror-source/</id>
    <published>2020-11-29T19:50:16.000Z</published>
    <updated>2022-04-05T08:05:52.184Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>一句话：从右边几个源中寻找对应软件即可 <a href="https://mirror.tuna.tsinghua.edu.cn/help/" target="_blank" rel="noopener">清华源</a>   <a href="https://mirrors.ustc.edu.cn/help/" target="_blank" rel="noopener">中科大源</a>  <a href="https://developer.aliyun.com/mirror/" target="_blank" rel="noopener">阿里源</a>   <a href="https://npm.taobao.org/mirrors" target="_blank" rel="noopener">淘宝源</a> 。当然如果能科学上网可忽略，不过大多数时候使用国内镜像源会更快一些。</p></blockquote><p>趁这波换电脑，顺便整理了一番个人日常开发中需要替换的国内镜像源。</p><h2 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h2><p><strong>pip</strong></p><p>以下命令会添加配置至<code>$HOME/.config/pip/pip.conf</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U</span><br><span class="line">pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple</span><br></pre></td></tr></table></figure><p><strong>pyenv</strong></p><p>修改v变量，一键安装对应Python版本</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v=3.8.3;wget https://npm.taobao.org/mirrors/python/$v/Python-$v.tar.xz -P $(pyenv root)/cache/;pyenv install $v</span><br></pre></td></tr></table></figure><h2 id="Golang"><a href="#Golang" class="headerlink" title="Golang"></a>Golang</h2><p><strong>go module</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">export GO111MODULE=on</span><br><span class="line">export GOPROXY='https://goproxy.cn'</span><br></pre></td></tr></table></figure><h2 id="Node-js"><a href="#Node-js" class="headerlink" title="Node.js"></a>Node.js</h2><p><strong>npm</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm config set registry https://registry.npm.taobao.org</span><br></pre></td></tr></table></figure><p><strong>yarn</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn config set registry https://registry.npm.taobao.org/</span><br></pre></td></tr></table></figure><hr><h2 id="Arch"><a href="#Arch" class="headerlink" title="Arch"></a>Arch</h2><p><strong>pacman</strong></p><p>覆盖原有<code>/etc/pacman.d/mirrorlist</code>，可以先备份原有文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch</span><br><span class="line">Server = http://mirrors.aliyun.com/archlinux/$repo/os/$arch</span><br></pre></td></tr></table></figure><p><strong>archlinuxcn</strong></p><p>添加至原<code>/etc/pacman.conf</code>末尾</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[archlinuxcn]</span><br><span class="line">Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch</span><br></pre></td></tr></table></figure><p><strong>AUR</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yay --aururl "https://aur.tuna.tsinghua.edu.cn" --save</span><br></pre></td></tr></table></figure><h2 id="Ubuntu"><a href="#Ubuntu" class="headerlink" title="Ubuntu"></a>Ubuntu</h2><p><strong>20.04LTS</strong></p><p>覆盖原有<code>/etc/apt/srouces.list</code></p><figure class="highlight shell"><table><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></pre></td><td class="code"><pre><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse</span><br></pre></td></tr></table></figure><p><code>18.04LTS</code>版本将所有 focal 替换成 bionic 即可</p><h2 id="Alpine"><a href="#Alpine" class="headerlink" title="Alpine"></a>Alpine</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories</span><br></pre></td></tr></table></figure><hr><h2 id="Homebrew"><a href="#Homebrew" class="headerlink" title="Homebrew"></a>Homebrew</h2><p><strong>Core</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"</span><br><span class="line">git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git</span><br></pre></td></tr></table></figure><p><strong>Cask</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-cask</span><br><span class="line">git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git</span><br></pre></td></tr></table></figure><hr><p>不定时更新中…</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;一句话：从右边几个源中寻找对应软件即可 &lt;a href=&quot;https://mirror.tuna.tsinghua.edu.cn/help/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;清华源&lt;/a&gt;   &lt;a href=&quot;ht
      
    
    </summary>
    
    
      <category term="工具" scheme="https://www.wzmmmmj.com/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
  </entry>
  
  <entry>
    <title>装机实录之选购篇</title>
    <link href="https://www.wzmmmmj.com/2020/11/22/diy-computer-setup/"/>
    <id>https://www.wzmmmmj.com/2020/11/22/diy-computer-setup/</id>
    <published>2020-11-22T20:49:21.000Z</published>
    <updated>2022-04-05T08:05:52.182Z</updated>
    
    <content type="html"><![CDATA[<p>作为早在去年双11嚷嚷着要装机的等等党本党，在NVIDIA 30系显卡/AMD 4代锐龙发布后，坐不住了，开启了首次装机之旅。在踩了一遍坑后，特地来水一篇，记录一些注意事项。</p><h3 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h3><blockquote><p>买前生产力：</p><p>我要搞机器学习，TensorFlow启动，顺便学学剪辑，Adobe启动。</p><p>买后打游戏：</p><p>狂战士信条真好玩，赛博朋克2077怎么又跳票了。</p></blockquote><p>选配件前，先明确装机用途，到底是臭打游戏的，还是臭打游戏，还是臭打游戏。大部分情况下，臭打游戏吃显卡，生产力吃CPU。不过游戏性能过得去，也勉强能搞搞生产力，反过来就不一定了。当然预算充足，适度游戏，可以考虑双修。</p><h3 id="预算"><a href="#预算" class="headerlink" title="预算"></a>预算</h3><blockquote><p>三千预算进卡吧, 加钱加到九万八</p></blockquote><p>预算只能说量力而行吧。按CPU与显卡占预算的70%，进行选购。例如小弟预算一万，其实选择锐龙3700x或英特尔i5-10400配3080可能更适合臭打游戏，奈何3080缺货，秉承买新不买旧，最终选择了锐龙5800x跟3070，被迫双修，预算也超了10%。</p><p>当然不差钱的话，<a href="https://rank.kkj.cn/" target="_blank" rel="noopener">天梯图</a>一逛，配置拉满即可，阿苏斯全家桶带回家。</p><h3 id="板U"><a href="#板U" class="headerlink" title="板U"></a>板U</h3><p>CPU与主板可以购买板U套装。主板根据CPU厂商，分为两大平台，不同主板对应CPU的插槽不同，大部分商家都会搭配可用的板U套装，因为限价原因，板U套餐都比分开购买相对更实惠一些。</p><h3 id="内存"><a href="#内存" class="headerlink" title="内存"></a>内存</h3><p>关于内存选择，涉及到内存颗粒以及内存频率等，就不误人子弟了，这里放一篇专业的，<a href="https://zhuanlan.zhihu.com/p/83502558" target="_blank" rel="noopener">如何挑选内存</a></p><h3 id="电源"><a href="#电源" class="headerlink" title="电源"></a>电源</h3><p>选定其余硬件后，通过每个配件的额定功率，适当加50%余量，即可推算出大致需要选择的电源功率，推荐使用<a href="https://outervision.com/power-supply-calculator" target="_blank" rel="noopener">功率计算器</a>，当然预算足够，可以适当买大功率的电源，毕竟电源保修时间长也不太容易坏，下次升级换代时，电源还可以接着用。</p><h3 id="机箱"><a href="#机箱" class="headerlink" title="机箱"></a>机箱</h3><p>虽然机箱本质上不会影响电脑性能，但在选择机箱时，除了考虑机箱外观，还需要额外考虑以下几点：</p><ol><li>支持的主板尺寸，主板根据大小分为ITX，M-ATX，ATX以及更大的E-ATX。</li><li>支持的显卡尺寸，如果前置水冷，可能支持的显卡尺寸会更小</li><li>是否支持一体式水冷</li><li>一体式水冷尺寸，240、280还是320</li><li>一体式水冷的摆放，有些机箱只支持前置水冷。</li><li>附赠的风扇个数</li><li>风道设计以及静音效果</li></ol><h3 id="RGB"><a href="#RGB" class="headerlink" title="RGB"></a>RGB</h3><blockquote><p>RGB提升电脑百分之六十的性能。</p></blockquote><p>轻度RGB确实赏心悦目，但需要选择侧透机箱，不要拿个静音机箱来哄人。R不RGB，纯看个人喜好，预算充足，就购买支持神光同步的主板以及ARGB风扇和内存条，足够花里胡哨了。但如果预算吃紧，RGB溢价10%，不如拿来提升显卡。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>具体如何选购电脑配件，参考下列文章</p><ul><li><a href="https://www.zhihu.com/people/MaxSam" target="_blank" rel="noopener">https://www.zhihu.com/people/MaxSam</a></li><li><a href="https://www.runningcheese.com/pc" target="_blank" rel="noopener">https://www.runningcheese.com/pc</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;作为早在去年双11嚷嚷着要装机的等等党本党，在NVIDIA 30系显卡/AMD 4代锐龙发布后，坐不住了，开启了首次装机之旅。在踩了一遍坑后，特地来水一篇，记录一些注意事项。&lt;/p&gt;
&lt;h3 id=&quot;用途&quot;&gt;&lt;a href=&quot;#用途&quot; class=&quot;headerlink&quot; 
      
    
    </summary>
    
    
    
      <category term="生活" scheme="https://www.wzmmmmj.com/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>gRPC之interceptor</title>
    <link href="https://www.wzmmmmj.com/2020/11/08/grpc-interceptor/"/>
    <id>https://www.wzmmmmj.com/2020/11/08/grpc-interceptor/</id>
    <published>2020-11-08T21:13:25.000Z</published>
    <updated>2022-04-05T08:05:52.184Z</updated>
    
    <content type="html"><![CDATA[<p>在Web服务中，除了实际的业务代码，经常还需要实现统一记录请求日志，权限管理或异常处理等功能，这些在web框架Gin或Django中可通过middleware实现，而gRPC中则可使用interceptor，对rpc请求或响应进行拦截处理。</p><p>gRPC服务端跟客户端均可实现各自的拦截器，根据rpc的两种请求方式可分为两种。</p><ul><li>Unary Interceptor（一元拦截器）</li><li>Stream Interceptor（流式拦截器）</li></ul><h2 id="一元拦截器"><a href="#一元拦截器" class="headerlink" title="一元拦截器"></a>一元拦截器</h2><p>对于一元服务器拦截器，只需要定义<code>UnaryServerInterceptor</code>方法即可，其中，<code>handler(ctx, req)</code>即调用rpc方法。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UnaryServerInterceptor <span class="function"><span class="keyword">func</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    ctx context.Context,    // rpc上下文</span></span></span><br><span class="line"><span class="function"><span class="params">    req <span class="keyword">interface</span>&#123;&#125;,        // rpc请求参数</span></span></span><br><span class="line"><span class="function"><span class="params">    info *UnaryServerInfo,  // rpc方法信息</span></span></span><br><span class="line"><span class="function"><span class="params">    handler UnaryHandler    // rpc方法本身</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="params">(<span class="keyword">interface</span>&#123;&#125;, error)</span></span>&#123;</span><br><span class="line"><span class="keyword">return</span> handler(ctx, req)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而对于一元客户端拦截器，一样需要定义一个方法<code>UnaryClientInterceptor</code>，其中执行<code>invoker()</code>才真正请求rpc。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> UnaryClientInterceptor <span class="function"><span class="keyword">func</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    ctx context.Context,  // rpc上下文</span></span></span><br><span class="line"><span class="function"><span class="params">    method <span class="keyword">string</span>,        // 调用方法名</span></span></span><br><span class="line"><span class="function"><span class="params">    req,                  // rpc请求参数</span></span></span><br><span class="line"><span class="function"><span class="params">    reply <span class="keyword">interface</span>&#123;&#125;,    // rpc响应结果</span></span></span><br><span class="line"><span class="function"><span class="params">    cc *ClientConn,       // 连接句柄</span></span></span><br><span class="line"><span class="function"><span class="params">    invoker UnaryInvoker, // 调用rpc方法本身</span></span></span><br><span class="line"><span class="function"><span class="params">    opts ...CallOption    // 调用配置</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> invoker(ctx, method, req, reply, cc, opts...)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一元拦截器的实现，根据调用handler或invoker的前后，可分为三部分：调用前预处理，调用rpc方法，调用后处理。</p><h2 id="流式拦截器"><a href="#流式拦截器" class="headerlink" title="流式拦截器"></a>流式拦截器</h2><p>流式拦截器的实现，与一元拦截器一致，实现提供的方法即可，方法参数含义如下：</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> StreamServerInterceptor <span class="function"><span class="keyword">func</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    srv <span class="keyword">interface</span>&#123;&#125;,        // rpc请求参数</span></span></span><br><span class="line"><span class="function"><span class="params">    ss ServerStream,        // 服务端stream对象</span></span></span><br><span class="line"><span class="function"><span class="params">    info *StreamServerInfo, // rpc方法信息</span></span></span><br><span class="line"><span class="function"><span class="params">    handler StreamHandler   // rpc方法本身</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> <span class="params">(err error)</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> handler(src, ss)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> StreamClientInterceptor <span class="function"><span class="keyword">func</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    ctx context.Context,   // rpc上下文</span></span></span><br><span class="line"><span class="function"><span class="params">    desc *StreamDesc,      // 流信息</span></span></span><br><span class="line"><span class="function"><span class="params">    cc *ClientConn,        // 连接句柄</span></span></span><br><span class="line"><span class="function"><span class="params">    method <span class="keyword">string</span>,         // 调用方法名</span></span></span><br><span class="line"><span class="function"><span class="params">    streamer Streamer,     // 调用rpc方法本身</span></span></span><br><span class="line"><span class="function"><span class="params">    opts ...CallOption     // 调用配置</span></span></span><br><span class="line"><span class="function"><span class="params">)</span><span class="params">(ClientStream, error)</span></span>&#123;</span><br><span class="line">    <span class="comment">// 流操作预处理</span></span><br><span class="line">    clientStream, err := streamer(ctx, desc, cc, method, opts...)</span><br><span class="line">    <span class="comment">// 根据某些条件，通过clientStream拦截流操作</span></span><br><span class="line">    <span class="keyword">return</span> clientStream, err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>与其他拦截器不同，客户端流式拦截器的实现分为两部分，流操作预处理和流操作拦截，其不能在事后进行rpc方法调用和后处理，只能通过ClientStream对象进行流操作拦截，例如根据特定的metadata，调用<code>ClientStream.CloseSend()</code>终止流操作。</p><h2 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h2><p>这里我们将上述四种拦截器都写一个简单输出请求日志的demo，看看实际效果</p><p>demo目录结构如下</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">rpc</span><br><span class="line">├── base.proto</span><br><span class="line">├── base</span><br><span class="line">│   ├── base.pb.go</span><br><span class="line">│   └── base_grpc.pb.go</span><br><span class="line">├── client</span><br><span class="line">│   └── main.go</span><br><span class="line">├── server</span><br><span class="line">│    └── main.go</span><br><span class="line">├── go.mod</span><br><span class="line">├── go.sum</span><br></pre></td></tr></table></figure><p>base.proto文件如下</p><figure class="highlight protobuf"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">syntax = <span class="string">"proto3"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> proto;</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span> go_package = <span class="string">"base;base"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">service</span> <span class="title">BaseService</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">rpc</span> GetTime (TimeRequest) <span class="keyword">returns</span> (TimeResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">  <span class="keyword">rpc</span> Streaming (stream StreamRequest) <span class="keyword">returns</span> (stream StreamResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">message TimeRequest &#123;&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">message TimeResponse &#123;</span></span><br><span class="line"><span class="function">  string time = 1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">StreamRequest</span></span>&#123;</span><br><span class="line">  <span class="built_in">string</span> input = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">StreamResponse</span></span>&#123;</span><br><span class="line">  <span class="built_in">string</span> output = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行命令<code>protoc --go_out=. --go-grpc_out=. base.prto</code>生成对应的pb文件。</p><p>server.go</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"log"</span></span><br><span class="line"><span class="string">"io"</span></span><br><span class="line"><span class="string">"net"</span></span><br><span class="line"><span class="string">"strconv"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc/reflection"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> service <span class="keyword">struct</span> &#123;</span><br><span class="line">pb.UnimplementedBaseServiceServer</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">listen, err := net.Listen(<span class="string">"tcp"</span>, <span class="string">":50051"</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line">s := grpc.NewServer(grpc.UnaryInterceptor(UnaryServerInterceptor), grpc.StreamInterceptor(StreamServerInterceptor))</span><br><span class="line">reflection.Register(s)</span><br><span class="line">pb.RegisterBaseServiceServer(s, &amp;service&#123;&#125;)</span><br><span class="line">s.Serve(listen)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">UnaryServerInterceptor</span><span class="params">(ctx context.Context, req <span class="keyword">interface</span>&#123;&#125;, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler)</span> <span class="params">(resp <span class="keyword">interface</span>&#123;&#125;, err error)</span></span> &#123;</span><br><span class="line">log.Println(<span class="string">"start unary"</span>)</span><br><span class="line">resp, err = handler(ctx, req)</span><br><span class="line">log.Printf(<span class="string">"end unary %v\n"</span>, resp)</span><br><span class="line"><span class="keyword">return</span> resp, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">StreamServerInterceptor</span><span class="params">(srv <span class="keyword">interface</span>&#123;&#125;, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">log.Println(<span class="string">"before stream"</span>)</span><br><span class="line">err := handler(srv, ss)</span><br><span class="line">log.Println(<span class="string">"after stream"</span>)</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 具体接口实现</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">GetTime</span><span class="params">(ctx context.Context, in *pb.TimeRequest)</span> <span class="params">(*pb.TimeResponse, error)</span></span> &#123;</span><br><span class="line">now := time.Now().Format(<span class="string">"2006-01-02 15:04:05"</span>)</span><br><span class="line"><span class="keyword">return</span> &amp;pb.TimeResponse&#123;Time: now&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">Streaming</span><span class="params">(stream pb.BaseService_StreamingServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> n := <span class="number">0</span>; ; &#123;</span><br><span class="line">res, err := stream.Recv()</span><br><span class="line"><span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">v, _ := strconv.Atoi(res.Input)</span><br><span class="line">        log.Println(v)</span><br><span class="line">n += v</span><br><span class="line">stream.Send(&amp;pb.StreamResponse&#123;Output: strconv.Itoa(n)&#125;)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>client.go</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"io"</span></span><br><span class="line"><span class="string">"log"</span></span><br><span class="line"><span class="string">"strconv"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">conn, err := grpc.Dial(<span class="string">":50051"</span>, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithUnaryInterceptor(UnaryClientInterceptor), grpc.WithStreamInterceptor(StreamClientInterceptor))</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line"></span><br><span class="line">c := pb.NewBaseServiceClient(conn)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 依次执行unary，stream</span></span><br><span class="line">    _, err = c.GetTime(context.Background(), &amp;pb.TimeRequest&#123;&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">"===============\n"</span>)</span><br><span class="line">    streaming(c)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">UnaryClientInterceptor</span><span class="params">(ctx context.Context, method <span class="keyword">string</span>, req, reply <span class="keyword">interface</span>&#123;&#125;, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">log.Println(<span class="string">"before unary"</span>)</span><br><span class="line">err := invoker(ctx, method, req, reply, cc, opts...)</span><br><span class="line">log.Printf(<span class="string">"end unary %v\n"</span>, reply)</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">StreamClientInterceptor</span><span class="params">(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method <span class="keyword">string</span>, streamer grpc.Streamer, opts ...grpc.CallOption)</span> <span class="params">(grpc.ClientStream, error)</span></span> &#123;</span><br><span class="line">log.Println(<span class="string">"before stream"</span>)</span><br><span class="line">clientStream, err := streamer(ctx, desc, cc, method, opts...)</span><br><span class="line">log.Println(<span class="string">"check metadata"</span>)</span><br><span class="line"><span class="keyword">return</span> clientStream, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">streaming</span><span class="params">(client pb.BaseServiceClient)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">stream, _ := client.Streaming(context.Background())</span><br><span class="line"><span class="keyword">for</span> n := <span class="number">0</span>; n &lt; <span class="number">10</span>; n++ &#123;</span><br><span class="line">log.Println(<span class="string">"Streaming Send:"</span>, n)</span><br><span class="line">err := stream.Send(&amp;pb.StreamRequest&#123;Input: strconv.Itoa(n)&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">res, err := stream.Recv()</span><br><span class="line"><span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">log.Println(<span class="string">"Streaming Recv:"</span>, res.Output)</span><br><span class="line">&#125;</span><br><span class="line">stream.CloseSend()</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>rpc目录下依次执行<code>go run server/main.go</code> 和 <code>go run client/main.go</code>，输出效果如下</p><p><img src="/images/grpc-interceptor.png" alt="输出结果"></p><p>可以明显看出StreamClientInterceptor则是在流处理开始时就输出了两次日志，其余三种拦截器则在请求前后输出两次。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>如果需要使用多个拦截器，grpc-go中提供了相应的四种链接器</p><ul><li><code>grpc.ChainUnaryInterceptor(i ...UnaryServerInterceptor)</code></li><li><code>grpc.ChainStreamInterceptor(i ...StreamServerInterceptor)</code></li><li><code>grpc.WithChainUnaryInterceptor(i ...UnaryClientInterceptor)</code></li><li><code>grpc.WithChainStreamInterceptor(i ...StreamClientInterceptor)</code></li></ul><p>如果grpc版本过老，可能还未提供chain api，可以使用第三方库<a href="https://github.com/grpc-ecosystem/go-grpc-middleware" target="_blank" rel="noopener">grpc-ecosystem/go-grpc-middleware</a>。除了链接器，库中还提供了许多常用的拦截器，例如grpc_zap，grpc_recovery等。当然，特殊需求也可以通过实现对应方法，实现自定义interceptor。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在Web服务中，除了实际的业务代码，经常还需要实现统一记录请求日志，权限管理或异常处理等功能，这些在web框架Gin或Django中可通过middleware实现，而gRPC中则可使用interceptor，对rpc请求或响应进行拦截处理。&lt;/p&gt;
&lt;p&gt;gRPC服务端跟客
      
    
    </summary>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/categories/gRPC/"/>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/tags/gRPC/"/>
    
  </entry>
  
  <entry>
    <title>Golang中实现平滑发版</title>
    <link href="https://www.wzmmmmj.com/2020/10/31/golang-gracefully-upgrade/"/>
    <id>https://www.wzmmmmj.com/2020/10/31/golang-gracefully-upgrade/</id>
    <published>2020-10-31T21:48:14.000Z</published>
    <updated>2022-04-05T08:05:52.184Z</updated>
    
    <content type="html"><![CDATA[<p>关于平滑发版，<a href="https://github.com/LKI" target="_blank" rel="noopener">紫月</a>写过一篇博客《<a href="https://liriansu.com/posts/2019-12-09-gracefully-upgrade/" target="_blank" rel="noopener">软件工程实践之平滑发版</a>》，其中讲解了为什么需要平滑发版以及服务中如何实现。总结下来，平滑发版中会经历的几个步骤：</p><ol><li>启动新进程，旧进程停止监听请求，新请求由新进程监听</li><li>旧进程等待已连接的请求结束</li><li>待请求全部执行完毕或超过超时时间，关闭进程</li></ol><p>而进程什么时候停止监听，什么时候关闭进程，则可以通过Signal来实现。<br>以k8s为例，在执行<code>kubectl rollout restart</code>后，</p><ol><li>旧pod会接收到SIGTERM信号，pod status变为Terminating</li><li>旧pod等待处理的中的请求结束</li><li>待处理中的请求结束或超过预设的<code>terminationGracePeriodSeconds</code>时间</li><li>旧pod接收到SIGKILL信号后被移除</li></ol><p>一般来说，通过守护进程进行部署的方式，只需要正确处理Signal，即可实现服务业务平滑发版，例如最近整的项目中，由四部分组成：</p><ul><li><a href="https://github.com/gin-gonic/gin" target="_blank" rel="noopener">gin</a>启动的web服务</li><li><a href="https://github.com/RichardKnop/machinery" target="_blank" rel="noopener">machinery</a>启动的异步任务</li><li><a href="https://github.com/robfig/cron" target="_blank" rel="noopener">cron</a>启动的定时任务</li><li><a href="https://github.com/grpc/grpc-go" target="_blank" rel="noopener">grpc-go</a>启动的grpc服务</li></ul><p>其中machinery本身就实现了gracefully shutdown，调用api即可，后文将依次介绍其他三个部分如何实现平滑发版。</p><h2 id="gin"><a href="#gin" class="headerlink" title="gin"></a>gin</h2><p>Gin官方提供了<a href="https://github.com/gin-gonic/examples/blob/master/graceful-shutdown/graceful-shutdown/server.go" target="_blank" rel="noopener">gracefully shutdown的例子</a>，代码如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"context"</span></span><br><span class="line">    <span class="string">"log"</span></span><br><span class="line">    <span class="string">"net/http"</span></span><br><span class="line">    <span class="string">"os"</span></span><br><span class="line">    <span class="string">"os/signal"</span></span><br><span class="line">    <span class="string">"syscall"</span></span><br><span class="line">    <span class="string">"time"</span></span><br><span class="line"></span><br><span class="line">    <span class="string">"github.com/gin-gonic/gin"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    router := gin.Default()</span><br><span class="line">    router.GET(<span class="string">"/"</span>, <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line">        time.Sleep(<span class="number">5</span> * time.Second)</span><br><span class="line">        c.String(http.StatusOK, <span class="string">"Hello World"</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    srv := &amp;http.Server&#123;</span><br><span class="line">        Addr:    <span class="string">":8080"</span>,</span><br><span class="line">        Handler: router,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">if</span> err := srv.ListenAndServe(); err != <span class="literal">nil</span> &amp;&amp; err != http.ErrServerClosed &#123;</span><br><span class="line">            log.Fatalf(<span class="string">"listen: %s\n"</span>, err)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;()</span><br><span class="line"></span><br><span class="line">    quit := <span class="built_in">make</span>(<span class="keyword">chan</span> os.Signal)</span><br><span class="line">    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)</span><br><span class="line">    &lt;-quit</span><br><span class="line">    log.Println(<span class="string">"Shutting down server..."</span>)</span><br><span class="line"></span><br><span class="line">    ctx, cancel := context.WithTimeout(context.Background(), <span class="number">5</span>*time.Second)</span><br><span class="line">    <span class="keyword">defer</span> cancel()</span><br><span class="line">    <span class="keyword">if</span> err := srv.Shutdown(ctx); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">"Server forced to shutdown:"</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">    log.Println(<span class="string">"Server exiting"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>例子中通过主goroutine通过<code>signal.Notify()</code>监听， <code>&lt;-quit</code>进行阻塞，另外一个goroutine启动http server，当接收到SIGINT或SIGTERM信号后，http.ListenAndServe()返回http.ErrServerClosed，此时httpserver不再接受新请求，并创建一个超时时间为5s的context，通过http.Server的<a href="https://golang.org/pkg/net/http/#Server.Shutdown" target="_blank" rel="noopener">Shutdown</a>，并阻塞等待连接释放或context超时。</p><h2 id="grpc-go"><a href="#grpc-go" class="headerlink" title="grpc-go"></a>grpc-go</h2><p>grpc-go中提供了<code>GracefulStop</code>，我们只需要监听Signal，接受到后，由GracefulStop来处理连接中的请求</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"log"</span></span><br><span class="line">    <span class="string">"net"</span></span><br><span class="line">    <span class="string">"os"</span></span><br><span class="line">    <span class="string">"os/signal"</span></span><br><span class="line">    <span class="string">"syscall"</span></span><br><span class="line"></span><br><span class="line">    <span class="string">"google.golang.org/grpc"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    listen, err := net.Listen(<span class="string">"tcp"</span>, <span class="string">":8080"</span>)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">    server := grpc.NewServer()</span><br><span class="line">    <span class="comment">// something...</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">if</span> err = server.Serve(listen); err != <span class="literal">nil</span> &#123;</span><br><span class="line">              <span class="built_in">panic</span>(err)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;()</span><br><span class="line">    quit := <span class="built_in">make</span>(<span class="keyword">chan</span> os.Signal, <span class="number">1</span>)</span><br><span class="line">    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)</span><br><span class="line">    &lt;-quit</span><br><span class="line">    log.Println(<span class="string">"Shutting down gRPC server..."</span>)</span><br><span class="line">    server.GracefulStop()</span><br><span class="line">    log.Println(<span class="string">"gRPC server exiting..."</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="cron"><a href="#cron" class="headerlink" title="cron"></a>cron</h2><p>robfig/cron包中虽然没提供关于gracefully shutdown的内容，至少提供了Start以及Stop方法，按照前几个实现的套路，我们可以自己动手实现一下gracefully shutdown。因为cron.Start本身会新起一个的goroutine启动server，所以我们在主goroutine中监听signal，在收到Signal后，执行cron.Stop()，而cron.Stop()会返回一个context，后续通过select监听context结束或者通过time.After等待超时，待当前所有定时任务结束后，进程结束。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">"log"</span></span><br><span class="line">    <span class="string">"os"</span></span><br><span class="line">    <span class="string">"os/signal"</span></span><br><span class="line">    <span class="string">"syscall"</span></span><br><span class="line">    <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    cronjob := cron.New()</span><br><span class="line">    cronjob.AddFunc(<span class="string">"@every 5s"</span>, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; time.Sleep(<span class="number">10</span> * time.Second) &#125;)</span><br><span class="line">    cronjob.Start()</span><br><span class="line"></span><br><span class="line">    quit := <span class="built_in">make</span>(<span class="keyword">chan</span> os.Signal, <span class="number">1</span>)</span><br><span class="line">    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)</span><br><span class="line">    &lt;-quit</span><br><span class="line">    ctx := cronjob.Stop()</span><br><span class="line">    log.Println(<span class="string">"Shutting down cron..."</span>)</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> &lt;-time.After(<span class="number">10</span> * time.Second):</span><br><span class="line">        log.Fatal(<span class="string">"Cron forced to shutdown..."</span>)</span><br><span class="line">    <span class="keyword">case</span> &lt;-ctx.Done():</span><br><span class="line">        log.Println(<span class="string">"Cron exiting..."</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本文只介绍了golang中如何实现平滑发版，一般框架都会给出关于gracefully shutdown的api，没有也没关系，套路就是正常启动server后，通过监听Signal并阻塞主goroutine，待接收到SIGNTERM等信号后，通过time.After或context.WithTimeout，作为超时处理，并等待进行中的goroutine全部结束。有兴趣可以查看各个类库中如何等待处理进行中的连接。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;关于平滑发版，&lt;a href=&quot;https://github.com/LKI&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;紫月&lt;/a&gt;写过一篇博客《&lt;a href=&quot;https://liriansu.com/posts/2019-12-09-gracef
      
    
    </summary>
    
    
    
      <category term="go" scheme="https://www.wzmmmmj.com/tags/go/"/>
    
  </entry>
  
  <entry>
    <title>Golang实现git-open</title>
    <link href="https://www.wzmmmmj.com/2020/09/20/git-open/"/>
    <id>https://www.wzmmmmj.com/2020/09/20/git-open/</id>
    <published>2020-09-20T23:44:28.000Z</published>
    <updated>2022-04-05T08:05:52.183Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://github.com/paulirish/git-open" target="_blank" rel="noopener">git-open</a>是GitHub上开源的一个git相关命令行工具，终端通过键入<code>git open</code>，会在浏览器中打开当前路径所在git项目的远端页面，支持多种代码托管平台，具体可查看官方文档。本质上git-open是一个shell脚本，核心代码仅有200+行。在无参数的前提下，主要有以下几步</p><ol><li>判断当前路径是否在git工作区内</li><li>获取当前branch的remote-url</li><li>转换remote-url为托管仓库的页面地址</li><li>根据不同平台，命令行唤起浏览器</li></ol><p>下文将依照以上步骤，通过Golang第三方库cobra来实现类似命令行功能，具体cobra用法，可移步<a href="https://github.com/spf13/cobra" target="_blank" rel="noopener">官方文档</a></p><blockquote><p>完整代码放置文章结尾，git open功能也已集成至<a href="https://github.com/ackerr/lab" target="_blank" rel="noopener">ackerr/lab</a>。 </p></blockquote><h2 id="Step-0"><a href="#Step-0" class="headerlink" title="Step.0"></a>Step.0</h2><p>首先建立好基础代码结构，并简单实现了两个工具函数</p><ul><li>GitCommand: 简单封装Golang中执行git命令行</li><li>Err: 返回error时，输出报错信息，直接退出程序</li></ul><p>具体代码如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"github.com/spf13/cobra"</span></span><br><span class="line"><span class="string">"os"</span></span><br><span class="line"><span class="string">"os/exec"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := rootCmd.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义命令</span></span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:  <span class="string">"git-open"</span>,</span><br><span class="line">Run: openCurrentRepo,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 核心逻辑</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">openCurrentRepo</span><span class="params">(cmd *cobra.Command, args []<span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 稍微封装一下git command</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GitCommand</span><span class="params">(args ...<span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">cmd := exec.Command(<span class="string">"git"</span>, args...)</span><br><span class="line">output, err := cmd.Output()</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">string</span>(output), err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 快捷退出</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Err</span><span class="params">(msg ...<span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">fmt.Println(msg...)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-1"><a href="#Step-1" class="headerlink" title="Step.1"></a>Step.1</h2><p>通过<code>git rev-parse</code>，获取当前路径与.git目录相关信息，例如以下命令</p><p><strong>判断当前路径是否在git工作区内</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rev-parse --is-inside-work-tree</span><br></pre></td></tr></table></figure><p><strong>获取.git目录路径</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rev-parse --git-dir</span><br></pre></td></tr></table></figure><p><strong>获取git项目绝对路径</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rev-parse --show-toplevel</span><br></pre></td></tr></table></figure><blockquote><p>更多git rev-parse命令可查看<a href="https://git-scm.com/docs/git-rev-parse" target="_blank" rel="noopener">官方文档</a></p></blockquote><p>从以上命令挑选一个判断当前是否在工作区中即可，伪代码如下</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentGitRepo</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">output, err := GitCommand(<span class="string">"rev-parse"</span>, <span class="string">"-q"</span>, <span class="string">"--show-toplevel"</span>)</span><br><span class="line"><span class="keyword">return</span> output, err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-2"><a href="#Step-2" class="headerlink" title="Step.2"></a>Step.2</h2><p>在git中，通过HEAD指针来指向当前分支或指向某个commit，来确定当前索引位置，记录在.git/HEAD文件中，而通过<code>git symbolic-ref</code>命令，可获取当前HEAD信息</p><p><strong>获取当前分支名</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git symbolic-ref --short HEAD</span><br></pre></td></tr></table></figure><p><strong>获取当前分支remote</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config branch.&lt;branch&gt;.remote</span><br></pre></td></tr></table></figure><p><strong>获取remote-url</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git ls-remote --get-url &lt;remote&gt;</span><br></pre></td></tr></table></figure><p>返回的remote-url即执行<code>git remote add &lt;remote-url&gt;</code>时，添加的那串url，一般有https或ssh两种形式，格式如下</p><figure class="highlight plain"><table><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"># ssh  </span><br><span class="line">git@github.com:Ackerr&#x2F;lab.git</span><br><span class="line"></span><br><span class="line"># https</span><br><span class="line">https:&#x2F;&#x2F;github.com&#x2F;Ackerr&#x2F;lab.git</span><br></pre></td></tr></table></figure><p>伪代码如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取当前分支，如果不存在则使用master</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentBranch</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line">branch, err := SymbolicRef(<span class="string">"HEAD"</span>, <span class="literal">true</span>)</span><br><span class="line"><span class="keyword">if</span> branch == <span class="string">""</span> || err != <span class="literal">nil</span> &#123;</span><br><span class="line">branch = <span class="string">"master"</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> branch</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行symbolic-ref命令</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">SymbolicRef</span><span class="params">(ref <span class="keyword">string</span>, short <span class="keyword">bool</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">args := []<span class="keyword">string</span>&#123;<span class="string">"symbolic-ref"</span>&#125;</span><br><span class="line"><span class="keyword">if</span> short &#123;</span><br><span class="line">args = <span class="built_in">append</span>(args, <span class="string">"--short"</span>)</span><br><span class="line">&#125;</span><br><span class="line">args = <span class="built_in">append</span>(args, ref)</span><br><span class="line">output, err := GitCommand(args...)</span><br><span class="line"><span class="keyword">return</span> firstLine(output), err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过分支获取当前remote，默认为origin</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentRemote</span><span class="params">(branch <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">remote, err := GitCommand(<span class="string">"config"</span>, fmt.Sprintf(<span class="string">"branch.%s.remote"</span>, branch))</span><br><span class="line">remote = firstLine(remote)</span><br><span class="line"><span class="keyword">if</span> remote == <span class="string">""</span> || err != <span class="literal">nil</span> &#123;</span><br><span class="line">remote = <span class="string">"origin"</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> remote</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过remote获取remote-url，如果使用错误的remote，则报错退出</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RemoteURL</span><span class="params">(remote <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">gitURL, err := GitCommand(<span class="string">"ls-remote"</span>, <span class="string">"--get-url"</span>, remote)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">Err(<span class="string">"git remote is not set for"</span>, remote)</span><br><span class="line">&#125;</span><br><span class="line">gitURL = firstLine(gitURL)</span><br><span class="line"><span class="keyword">if</span> gitURL == remote &#123;</span><br><span class="line">Err(remote, <span class="string">"is a wrong remote"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> gitURL</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除末尾换行符</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">firstLine</span><span class="params">(output <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> i := strings.Index(output, <span class="string">"\n"</span>); i &gt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> output[<span class="number">0</span>:i]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> output</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的一点，通过exec执行的git命令，返回值会在末尾添加换行符’\n’，所以每个命令结束都需要调用firstLine移除</p></blockquote><h2 id="Step-3"><a href="#Step-3" class="headerlink" title="Step.3"></a>Step.3</h2><p>正常情况下，远端仓库页面地址结构为 <code>&lt;protocol&gt;/&lt;domain&gt;/&lt;repo path&gt;</code>，一般情况下protocol为https，GitLab与GitHub类型，转换代码如下</p><figure class="highlight go"><table><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">// for example:</span></span><br><span class="line"><span class="comment">// git@github.com/Ackerr:lab.git     -&gt; https://github.com/Ackerr/lab            </span></span><br><span class="line"><span class="comment">// https://github.com/Ackerr/lab.git -&gt; https://github.com/Ackerr/lab</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TransferToURL</span><span class="params">(gitURL <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;                                         </span><br><span class="line">    <span class="keyword">var</span> url <span class="keyword">string</span></span><br><span class="line">    <span class="keyword">if</span> strings.HasPrefix(gitURL, <span class="string">"https://"</span>) &#123;</span><br><span class="line">        url = gitURL[:<span class="built_in">len</span>(gitURL)<span class="number">-4</span>]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> strings.HasPrefix(gitURL, <span class="string">"git@"</span>) &#123;</span><br><span class="line">        url = gitURL[:<span class="built_in">len</span>(gitURL)<span class="number">-4</span>]</span><br><span class="line">        url = strings.Replace(url, <span class="string">":"</span>, <span class="string">"/"</span>, <span class="number">1</span>)</span><br><span class="line">        url = strings.Replace(url, <span class="string">"git@"</span>, <span class="string">"https://"</span>, <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> url</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-4"><a href="#Step-4" class="headerlink" title="Step.4"></a>Step.4</h2><p>对于不同操作系统，可能使用不同的命令唤起浏览器，例如macOS默认通过open，而windows则通过start，当然也可以通过设置环境变量$BROWSER，切换默认命令，代码如下</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">searchBrowserLauncher</span><span class="params">()</span> <span class="params">(browser <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> runtime.GOOS &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">"darwin"</span>:</span><br><span class="line">        browser = <span class="string">"open"</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">"windows"</span>:</span><br><span class="line">        browser = <span class="string">"cmd /c start"</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">"linux"</span>:</span><br><span class="line">        browser = <span class="string">"xdg-open"</span></span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        browser = <span class="string">""</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> browser</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过以上几步，git open的核心伪代码大致完成，接下来微调即可，完整代码如下</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"errors"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"github.com/kballard/go-shellquote"</span></span><br><span class="line"><span class="string">"github.com/spf13/cobra"</span></span><br><span class="line"><span class="string">"os"</span></span><br><span class="line"><span class="string">"os/exec"</span></span><br><span class="line"><span class="string">"runtime"</span></span><br><span class="line"><span class="string">"strings"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := rootCmd.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:  <span class="string">"git-open"</span>,</span><br><span class="line">Run: openCurrentRepo,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">openCurrentRepo</span><span class="params">(cmd *cobra.Command, args []<span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> remote <span class="keyword">string</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(args) &gt; <span class="number">0</span> &#123;</span><br><span class="line">remote = args[<span class="number">0</span>]</span><br><span class="line">&#125;</span><br><span class="line">_, err := CurrentGitRepo()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">Err(<span class="string">"not a git repository"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> remote == <span class="string">""</span> &#123;</span><br><span class="line">branch := CurrentBranch()</span><br><span class="line">remote = CurrentRemote(branch)</span><br><span class="line">&#125;</span><br><span class="line">gitURL := RemoteURL(remote)</span><br><span class="line">url := TransferToURL(gitURL)</span><br><span class="line">_ = OpenBrowser(url)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GitCommand</span><span class="params">(args ...<span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">cmd := exec.Command(<span class="string">"git"</span>, args...)</span><br><span class="line">cmd.Stderr = os.Stderr</span><br><span class="line">output, err := cmd.Output()</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">string</span>(output), err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取当前git项目路径</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentGitRepo</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">output, err := GitCommand(<span class="string">"rev-parse"</span>, <span class="string">"-q"</span>, <span class="string">"--show-toplevel"</span>)</span><br><span class="line"><span class="keyword">return</span> output, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取当前分支，如果不存在则使用master</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentBranch</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line">branch, err := SymbolicRef(<span class="string">"HEAD"</span>, <span class="literal">true</span>)</span><br><span class="line"><span class="keyword">if</span> branch == <span class="string">""</span> || err != <span class="literal">nil</span> &#123;</span><br><span class="line">branch = <span class="string">"master"</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> branch</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">SymbolicRef</span><span class="params">(ref <span class="keyword">string</span>, short <span class="keyword">bool</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">args := []<span class="keyword">string</span>&#123;<span class="string">"symbolic-ref"</span>&#125;</span><br><span class="line"><span class="keyword">if</span> short &#123;</span><br><span class="line">args = <span class="built_in">append</span>(args, <span class="string">"--short"</span>)</span><br><span class="line">&#125;</span><br><span class="line">args = <span class="built_in">append</span>(args, ref)</span><br><span class="line">output, err := GitCommand(args...)</span><br><span class="line"><span class="keyword">return</span> firstLine(output), err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过分支获取当前remote，默认为origin</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CurrentRemote</span><span class="params">(branch <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">remote, err := GitCommand(<span class="string">"config"</span>, fmt.Sprintf(<span class="string">"branch.%s.remote"</span>, branch))</span><br><span class="line">remote = firstLine(remote)</span><br><span class="line"><span class="keyword">if</span> remote == <span class="string">""</span> || err != <span class="literal">nil</span> &#123;</span><br><span class="line">remote = <span class="string">"origin"</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> remote</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过remote获取remote-url，如果使用错误的remote，则报错退出</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RemoteURL</span><span class="params">(remote <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">gitURL, err := GitCommand(<span class="string">"ls-remote"</span>, <span class="string">"--get-url"</span>, remote)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">Err(<span class="string">"git remote is not set for"</span>, remote)</span><br><span class="line">&#125;</span><br><span class="line">gitURL = firstLine(gitURL)</span><br><span class="line"><span class="keyword">if</span> gitURL == remote &#123;</span><br><span class="line">Err(remote, <span class="string">"is a wrong remote"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> gitURL</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除末尾换行符</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">firstLine</span><span class="params">(output <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> i := strings.Index(output, <span class="string">"\n"</span>); i &gt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> output[<span class="number">0</span>:i]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> output</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// remote url转换为web url</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TransferToURL</span><span class="params">(gitURL <span class="keyword">string</span>)</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> url <span class="keyword">string</span></span><br><span class="line"><span class="keyword">if</span> strings.HasPrefix(gitURL, <span class="string">"https://"</span>) &#123;</span><br><span class="line">url = gitURL[:<span class="built_in">len</span>(gitURL)<span class="number">-4</span>]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> strings.HasPrefix(gitURL, <span class="string">"git@"</span>) &#123;</span><br><span class="line">url = gitURL[:<span class="built_in">len</span>(gitURL)<span class="number">-4</span>]</span><br><span class="line">url = strings.Replace(url, <span class="string">":"</span>, <span class="string">"/"</span>, <span class="number">1</span>)</span><br><span class="line">url = strings.Replace(url, <span class="string">"git@"</span>, <span class="string">"https://"</span>, <span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> url</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出报错，结束程序</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Err</span><span class="params">(msg ...<span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">fmt.Println(msg...)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 唤起浏览器，打开url</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">OpenBrowser</span><span class="params">(url <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">launcher, err := browserLauncher()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">args := <span class="built_in">append</span>(launcher, url)</span><br><span class="line">cmd := exec.Command(args[<span class="number">0</span>], args[<span class="number">1</span>:]...)</span><br><span class="line"><span class="keyword">return</span> cmd.Run()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 切分命令为数组，方便exec.Command执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">browserLauncher</span><span class="params">()</span> <span class="params">([]<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">browser := os.Getenv(<span class="string">"BROWSER"</span>)</span><br><span class="line"><span class="keyword">if</span> browser == <span class="string">""</span> &#123;</span><br><span class="line">browser = searchBrowserLauncher()</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">browser = os.ExpandEnv(browser)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> browser == <span class="string">""</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, errors.New(<span class="string">"please set $BROWSER to a web launcher"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> shellquote.Split(browser)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据操作系统，返回默认browser command</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">searchBrowserLauncher</span><span class="params">()</span> <span class="params">(browser <span class="keyword">string</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> runtime.GOOS &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">"darwin"</span>:</span><br><span class="line">browser = <span class="string">"open"</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">"windows"</span>:</span><br><span class="line">browser = <span class="string">"cmd /c start"</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">"linux"</span>:</span><br><span class="line">browser = <span class="string">"xdg-open"</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">browser = <span class="string">""</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> browser</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后通过<code>go build -o git-open</code>，即可通过git-open，在浏览器中打开当前项目的远端页面。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;a href=&quot;https://github.com/paulirish/git-open&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;git-open&lt;/a&gt;是GitHub上开源的一个git相关命令行工具，终端通过键入&lt;code&gt;git open&lt;/
      
    
    </summary>
    
    
    
      <category term="git" scheme="https://www.wzmmmmj.com/tags/git/"/>
    
      <category term="go" scheme="https://www.wzmmmmj.com/tags/go/"/>
    
      <category term="cli" scheme="https://www.wzmmmmj.com/tags/cli/"/>
    
  </entry>
  
  <entry>
    <title>gRPC之stream</title>
    <link href="https://www.wzmmmmj.com/2020/09/06/grpc-stream/"/>
    <id>https://www.wzmmmmj.com/2020/09/06/grpc-stream/</id>
    <published>2020-09-06T23:48:06.000Z</published>
    <updated>2022-04-05T08:05:52.184Z</updated>
    
    <content type="html"><![CDATA[<p>HTTP/2中有两个概念，流（stream）与帧（frame），其中帧作为HTTP/2中通信的最小传输单位，通常一个请求或响应会被分为一个或多个帧传输，流则表示已建立连接的虚拟通道，可以传输多次请求或响应。每个帧中包含<strong>Stream Identifier</strong>，标志所属流。HTTP/2通过流与帧实现多路复用，对于相同域名的请求，通过<strong>Stream Identifier</strong>标识可在同一个流中进行，从而减少连接开销。 而gRPC基于HTTP/2协议传输，自然而然也实现了流式传输，其中gRPC中共有以下三种类型的流</p><ol><li>服务端流式响应</li><li>客户端流式请求</li><li>两端双向流式</li></ol><p>本篇主要讲讲如何实现gRPC三种流式处理。</p><h2 id="Proto"><a href="#Proto" class="headerlink" title="Proto"></a>Proto</h2><p>通过在请求体或响应体前添加关键词<code>stream</code>，即可定义该消息体为流传输，<code>base.proto</code>如下所示</p><figure class="highlight protobuf"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">syntax = <span class="string">"proto3"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> proto;</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span> go_package = <span class="string">"base;base"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">service</span> <span class="title">BaseService</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 服务端流式响应</span></span><br><span class="line">    <span class="function"><span class="keyword">rpc</span> ServerStream (StreamRequest) <span class="keyword">returns</span> (stream StreamResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">    // 客户端流式请求</span></span><br><span class="line"><span class="function">    <span class="keyword">rpc</span> ClientStream (stream StreamRequest) <span class="keyword">returns</span> (StreamResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">    // 双向流式</span></span><br><span class="line"><span class="function">    <span class="keyword">rpc</span> Streaming (stream StreamRequest) <span class="keyword">returns</span> (stream StreamResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">message StreamRequest&#123;</span></span><br><span class="line"><span class="function">  string input = 1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">message</span> <span class="title">StreamResponse</span></span>&#123;</span><br><span class="line">  <span class="built_in">string</span> output = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行<code>protoc --go_out=. --go-grpc_out=. stream.prto</code>，生成对应的Go Stub。</p><h2 id="基础代码"><a href="#基础代码" class="headerlink" title="基础代码"></a>基础代码</h2><p>这里先将附上代码模板<br>server.go</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"net"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc/reflection"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> service <span class="keyword">struct</span> &#123;</span><br><span class="line">pb.UnimplementedBaseServiceServer</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">listen, err := net.Listen(<span class="string">"tcp"</span>, <span class="string">":50051"</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line">s := grpc.NewServer()</span><br><span class="line">reflection.Register(s)</span><br><span class="line">pb.RegisterBaseServiceServer(s, &amp;service&#123;&#125;)</span><br><span class="line">s.Serve(listen)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">ClientStream</span><span class="params">(stream pb.BaseService_ClientStreamServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span> </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">ServerStream</span><span class="params">(in *pb.StreamRequest, stream pb.BaseService_ServerStreamServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">Streaming</span><span class="params">(stream pb.BaseService_StreamingServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>client.go</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"io"</span></span><br><span class="line"><span class="string">"strconv"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">conn, err := grpc.Dial(<span class="string">":50051"</span>, grpc.WithInsecure(), grpc.WithBlock())</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line"></span><br><span class="line">c := pb.NewBaseServiceClient(conn)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">clientStream</span><span class="params">(client pb.BaseServiceClient, input <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">serverStream</span><span class="params">(client pb.BaseServiceClient, r *pb.StreamRequest)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">stream</span><span class="params">(client pb.BaseServiceClient)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="服务端流式响应"><a href="#服务端流式响应" class="headerlink" title="服务端流式响应"></a>服务端流式响应</h2><p>与普通的gRPC代码不同的是，普通gRPC一次请求只会返回一次响应，而服务端流式响应通过调用<code>stream.Send()</code>，返回多次``StreamResponse`</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">ServerStream</span><span class="params">(in *pb.StreamRequest, stream pb.BaseService_ServerStreamServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    input := in.Input</span><br><span class="line">    <span class="keyword">var</span> output <span class="keyword">string</span></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(input); i++ &#123;</span><br><span class="line">      output = fmt.Sprintf(<span class="string">"index: %d, result: %s"</span>, i, <span class="keyword">string</span>(input[i]))</span><br><span class="line">     stream.Send(&amp;pb.StreamResponse&#123;Output: output&#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>客户端代码如下，通过for循环调用<code>stream.Recv()</code>，等待接收服务端响应并阻塞，直到出错或流结束。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 客户端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">serverStream</span><span class="params">(client pb.BaseServiceClient, r *pb.StreamRequest)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">"Server Stream Send:"</span>, r.Input)</span><br><span class="line">    stream, _ := client.ServerStream(context.Background(), r)</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        res, err := stream.Recv()</span><br><span class="line">        <span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> err</span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Println(<span class="string">"Server Stream Recv:"</span>, res.Output)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动服务端，客户端执行<code>serverStream(c, &amp;pb.StreamRequest{Input: &quot;something&quot;})</code>，输出如下：</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">Server Stream Send: something</span><br><span class="line">Server Stream Recv: index: 0, result: s</span><br><span class="line">Server Stream Recv: index: 1, result: o</span><br><span class="line">Server Stream Recv: index: 2, result: m</span><br><span class="line">Server Stream Recv: index: 3, result: e</span><br><span class="line">Server Stream Recv: index: 4, result: t</span><br><span class="line">Server Stream Recv: index: 5, result: h</span><br><span class="line">Server Stream Recv: index: 6, result: i</span><br><span class="line">Server Stream Recv: index: 7, result: n</span><br><span class="line">Server Stream Recv: index: 8, result: g</span><br></pre></td></tr></table></figure><p>不难看出，客户端一次请求，服务端将数据多次返回，从而实现服务端流式响应</p><h2 id="客户端流式请求"><a href="#客户端流式请求" class="headerlink" title="客户端流式请求"></a>客户端流式请求</h2><p>与服务端流式响应类似，只不过转变为服务端for循环调用<code>stream.Rece()</code>，接收客户端消息并阻塞，等客户端调用<code>stream.CloseAndRecv()</code>，关闭流的发送后进入阻塞监听，服务端调用<code>stream.SendAndClose()</code>，返回响应体并关闭流。此方式客户端只负责发送流的结束，服务端可以在中途结束整个流处理。</p><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">ClientStream</span><span class="params">(stream pb.BaseService_ClientStreamServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    output := <span class="string">""</span></span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        r, err := stream.Recv()</span><br><span class="line">        <span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line">          <span class="keyword">return</span> stream.SendAndClose(&amp;pb.StreamResponse&#123;Output: output&#125;)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">          fmt.Println(err)</span><br><span class="line">        &#125;</span><br><span class="line">        output += r.Input</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 客户端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">clientStream</span><span class="params">(client pb.BaseServiceClient, input <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    stream, _ := client.ClientStream(context.Background())</span><br><span class="line">    <span class="keyword">for</span> _, s := <span class="keyword">range</span> input &#123;</span><br><span class="line">        fmt.Println(<span class="string">"Client Stream Send:"</span>, <span class="keyword">string</span>(s))</span><br><span class="line">        err := stream.Send(&amp;pb.StreamRequest&#123;Input: <span class="keyword">string</span>(s)&#125;)</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">           <span class="keyword">return</span> err</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    res, err := stream.CloseAndRecv()</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">       fmt.Println(err)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">"Client Stream Recv:"</span>, res.Output)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>客户端执行<code>clientStream(c, &quot;something&quot;)</code>，输出如下：</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">Client Stream Send: s</span><br><span class="line">Client Stream Send: o</span><br><span class="line">Client Stream Send: m</span><br><span class="line">Client Stream Send: e</span><br><span class="line">Client Stream Send: t</span><br><span class="line">Client Stream Send: h</span><br><span class="line">Client Stream Send: i</span><br><span class="line">Client Stream Send: n</span><br><span class="line">Client Stream Send: g</span><br><span class="line">Client Stream Recv: something</span><br></pre></td></tr></table></figure><h2 id="双向流式"><a href="#双向流式" class="headerlink" title="双向流式"></a>双向流式</h2><p>由客户端发送流式请求，服务端通过流式响应，但具体的交互方式，根据编写的逻辑不同，效果也不同，类似于聊天室，开启聊天室后，怎么回复，回复多少，何时关闭，谁来关闭，根据适用场景而定。以下事例代码，客户端通过发送0-10，服务端累加返回。</p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 客户端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">streaming</span><span class="params">(client pb.BaseServiceClient)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    stream, _ := client.Streaming(context.Background())</span><br><span class="line">    <span class="keyword">for</span> n := <span class="number">0</span>; n &lt; <span class="number">10</span>; n++ &#123;</span><br><span class="line">        fmt.Println(<span class="string">"Streaming Send:"</span>, n)</span><br><span class="line">        err := stream.Send(&amp;pb.StreamRequest&#123;Input: strconv.Itoa(n)&#125;)</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> err</span><br><span class="line">        &#125;</span><br><span class="line">        res, err := stream.Recv()</span><br><span class="line">        <span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> err</span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Println(<span class="string">"Streaming Recv:"</span>, res.Output)</span><br><span class="line">    &#125;</span><br><span class="line">    stream.CloseSend()</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><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">// 服务端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">Streaming</span><span class="params">(stream pb.BaseService_StreamingServer)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> n := <span class="number">0</span>; ; &#123;</span><br><span class="line">      res, err := stream.Recv()</span><br><span class="line">      <span class="keyword">if</span> err == io.EOF &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">          <span class="keyword">return</span> err</span><br><span class="line">      &#125;</span><br><span class="line">      v, _ := strconv.Atoi(res.Input)</span><br><span class="line">      n += v</span><br><span class="line">      stream.Send(&amp;pb.StreamResponse&#123;Output: strconv.Itoa(n)&#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>客户端执行<code>streaming(c)</code>，输出如下：</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">Streaming Send: 0</span><br><span class="line">Streaming Recv: 0</span><br><span class="line">Streaming Send: 1</span><br><span class="line">Streaming Recv: 1</span><br><span class="line">Streaming Send: 2</span><br><span class="line">Streaming Recv: 3</span><br><span class="line">Streaming Send: 3</span><br><span class="line">Streaming Recv: 6</span><br><span class="line">Streaming Send: 4</span><br><span class="line">Streaming Recv: 10</span><br><span class="line">.....</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本篇只是写了gRPC三种流式处理的简单demo，实际情况下，根据业务背景来选择该使用哪种流。例如双向流就类似于聊天室，或保持长连接类型，而传输数量量大时，可选择单向流，让接收方批量处理。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://colobu.com/2017/04/06/dive-into-gRPC-streaming/" target="_blank" rel="noopener">gRPC那些事之Streaming</a></li><li><a href="https://segmentfault.com/a/1190000016503114" target="_blank" rel="noopener">带入gRPC：gRPC Streaming</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;HTTP/2中有两个概念，流（stream）与帧（frame），其中帧作为HTTP/2中通信的最小传输单位，通常一个请求或响应会被分为一个或多个帧传输，流则表示已建立连接的虚拟通道，可以传输多次请求或响应。每个帧中包含&lt;strong&gt;Stream Identifier&lt;/s
      
    
    </summary>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/categories/gRPC/"/>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/tags/gRPC/"/>
    
  </entry>
  
  <entry>
    <title>初识gRPC</title>
    <link href="https://www.wzmmmmj.com/2020/08/23/rpc-and-grpc/"/>
    <id>https://www.wzmmmmj.com/2020/08/23/rpc-and-grpc/</id>
    <published>2020-08-23T23:56:46.000Z</published>
    <updated>2022-04-05T08:05:52.187Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近我司技术栈新增加了gRPC，小弟有幸使用gRPC实现了几个业务需求，本篇文章记录对RPC以及gRPC的一些浅显认知。</p><h2 id="RPC"><a href="#RPC" class="headerlink" title="RPC"></a>RPC</h2><p>RPC，全称<code>remote procedure call</code>，即远程过程调用，也是一种客户端-服务器（client/server）模型，通俗讲RPC就是客户端像调用本地方法一样调用了远端服务中的方法，而无需关心具体调用细节。</p><p>这里先看看本地是如何调用函数的，伪代码如下：</p><figure class="highlight python"><table><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"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(x, y)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> x + y</span><br><span class="line"></span><br><span class="line">a, b = <span class="number">5</span>, <span class="number">10</span></span><br><span class="line">result = add(a, b)</span><br></pre></td></tr></table></figure><p>执行上述代码，大致经过以下步骤</p><ol><li>定义a,b变量并赋值</li><li>执行add函数，将a,b值，赋值给x,y</li><li>执行x+y</li><li>退出add函数，将结果赋值给result</li></ol><p>以上4步就是本地调用的过程。在不考虑调用函数逻辑情况下，调用RPC方法体验与本地函数调用基本一致。</p><p>不过因为RPC涉及网络调用，服务两端并不存在一个内存空间内，这样就会带来一些新问题</p><p><strong>1.服务之间方法如何映射？</strong></p><p>本地调用通过函数指针指定调用的函数，而RPC调用为了确保执行正确的函数，则需要客户端与服务端有一致函数映射，确保服务端执行的是客户端调用的，简单实现可以用过双方维护一套映射表，一般通过动态代理实现。</p><p><strong>2.服务之间传输的信息结构？</strong></p><p>本地调用只需将参数压入栈中，函数从栈中读取即可。而RPC调用，因为不在相同的内存空间，无法通过内存传递参数，并且需要通过网络传输，则需要一套序列化/反序列化机制，确保服务端将收到的 客户端的序列化后的参数 反序列化后 与 实际传参 意义一致。</p><p><strong>3.服务之间如何通信？</strong></p><p>客户端需要通过网络传输，将函数映射以及序列化后的内容发送给服务端，而服务端则需要返回内容给客户端。中间则需要选择传输协议，可以是传输层协议，也可以是应用层协议，如gRPC就使用HTTP/2。</p><p>解决了上述三个问题，也就实现了一个基础的RPC框架。</p><h3 id="RPC调用流程"><a href="#RPC调用流程" class="headerlink" title="RPC调用流程"></a>RPC调用流程</h3><p>下图为一个完成的RPC过程：</p><p><img src="https://static001.geekbang.org/resource/image/82/59/826a6da653c4093f3dc3f0a833915259.jpg" alt="image"></p><blockquote><p>笼统的讲，封装好的HTTP接口形式也可算作RPC的一种</p></blockquote><h2 id="gRPC"><a href="#gRPC" class="headerlink" title="gRPC"></a>gRPC</h2><p>gRPC是google与2015年开源的RPC框架，相比于其他RPC框架，最显著的三大特点：</p><ol><li><code>ProtoBuf</code>作为默认IDL</li><li>基于<code>HTTP/2</code>协议，具体HTTP/2实现可移步<a href="http://lxkaka.wang/http2/" target="_blank" rel="noopener">lxkaka</a></li><li>支持多种开发语言</li></ol><p><img src="https://colobu.com/2017/04/06/dive-into-gRPC-streaming/gRPC.png" alt="gRPC交互流程"></p><h3 id="gRPC-vs-REST"><a href="#gRPC-vs-REST" class="headerlink" title="gRPC vs REST"></a>gRPC vs REST</h3><p><strong>高性能</strong></p><p>基于HTTP/2的多路复用，减少TCP连接，首部压缩等特性以及ProtoBuf高效的二进制序列化，更高效的传输消息，节省带宽，从而提高吞吐量。</p><p><strong>流处理</strong></p><p>HTTP/2为实时通信流提供了基础。除了简单的RPC，gRPC中支持三种流组合：</p><ul><li>服务器流式响应</li><li>客户端流式发送</li><li>双向流</li></ul><p><strong>开发流程简单</strong></p><p>相比于RESTful接口，无需思考请求方式，URL路径等规范，定义好proto请求响应结构，生成对应pb文件即可。</p><p><strong>支持多语言</strong></p><p>只需编写一份proto，通过命令行生成对应语言的Stub代码即可调用，而RESTful接口，每个调用方都需要实现一套基础调用类。</p><h3 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h3><p>体验下来，gRPC大致的开发流程如下：</p><ol><li>定义proto文件，即定义Request和Response结构，以及包含多个方法的服务Service</li><li>通过protoc工具生成对应语言的Stub</li><li>server实现proto中定义的接口，编写逻辑代码</li><li>client通过生成的Stub调用server</li></ol><h3 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h3><p>Go编写一个获取服务器时间的rpc接口，示例代码如下：</p><p><strong>time.proto</strong></p><figure class="highlight protobuf"><table><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">syntax = <span class="string">"proto3"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> proto;</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span> go_package = <span class="string">"base;base"</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">service</span> <span class="title">BaseService</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">rpc</span> GetTime (TimeRequest) <span class="keyword">returns</span> (TimeResponse)&#123;&#125;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">message TimeRequest &#123;&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">message TimeResponse &#123;</span></span><br><span class="line"><span class="function">  string time = 1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过protoc生成服务端以及客户端的Stub代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">protoc --go_out&#x3D;. --go-grpc_out&#x3D;.  time.proto</span><br></pre></td></tr></table></figure><p><strong>server.go</strong></p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"net"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line"><span class="string">"google.golang.org/grpc/reflection"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> service <span class="keyword">struct</span> &#123;</span><br><span class="line">pb.UnimplementedBaseServiceServer</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">listen, err := net.Listen(<span class="string">"tcp"</span>, <span class="string">":50051"</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line">s := grpc.NewServer()</span><br><span class="line">reflection.Register(s)</span><br><span class="line">pb.RegisterBaseServiceServer(s, &amp;service&#123;&#125;)</span><br><span class="line">s.Serve(listen)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *service)</span> <span class="title">GetTime</span><span class="params">(ctx context.Context, in *pb.TimeRequest)</span> <span class="params">(*pb.TimeResponse, error)</span></span> &#123;</span><br><span class="line">now := time.Now().Format(<span class="string">"2006-01-02 15:04:05"</span>)</span><br><span class="line"><span class="keyword">return</span> &amp;pb.TimeResponse&#123;Time: now&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>client.go</strong></p><figure class="highlight go"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"context"</span></span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"time"</span></span><br><span class="line"></span><br><span class="line">pb <span class="string">"rpc/base"</span></span><br><span class="line"></span><br><span class="line"><span class="string">"google.golang.org/grpc"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">conn, err := grpc.Dial(<span class="string">":50051"</span>, grpc.WithInsecure(), grpc.WithBlock())</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line"></span><br><span class="line">c := pb.NewBaseServiceClient(conn)</span><br><span class="line"></span><br><span class="line">getTime(c)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getTime</span><span class="params">(client pb.BaseServiceClient)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">ctx, cancel := context.WithTimeout(context.Background(), time.Second)</span><br><span class="line"><span class="keyword">defer</span> cancel()</span><br><span class="line">r, _ := client.GetTime(ctx, &amp;pb.TimeRequest&#123;&#125;)</span><br><span class="line">fmt.Println(<span class="string">"Time:"</span>, r.Time)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>对于简单的需求实现，gRPC很容易上手，编写protobuf并不需要额外去了解什么语法，类似interface。而如果要实现gRPC高可用，还有很多路要走。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://grpc.io/docs/what-is-grpc/introduction/" target="_blank" rel="noopener">gRPC官方文档</a></li><li><a href="https://www.zhihu.com/question/25536695" target="_blank" rel="noopener">通俗解释一下什么是RPC</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;最近我司技术栈新增加了gRPC，小弟有幸使用gRPC实现了几个业务需求，本篇文章记录对RPC以及gRPC的一些浅显认知。&lt;/p&gt;
&lt;h2 i
      
    
    </summary>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/categories/gRPC/"/>
    
    
      <category term="gRPC" scheme="https://www.wzmmmmj.com/tags/gRPC/"/>
    
  </entry>
  
  <entry>
    <title>深入浅出Redis之ziplist</title>
    <link href="https://www.wzmmmmj.com/2020/08/02/redis-ziplist/"/>
    <id>https://www.wzmmmmj.com/2020/08/02/redis-ziplist/</id>
    <published>2020-08-02T21:55:29.000Z</published>
    <updated>2022-04-05T08:05:52.187Z</updated>
    
    <content type="html"><![CDATA[<p>Ziplist是Redis中为了节约内存空间而实现的顺序性数据结构，通过一系列特殊编码，将数据与其编码信息存储在连续内存中，一个ziplist可以包含多个节点，每个节点可存储一个字节数组或一个整数值，其中对于节点值类型存储还采用了变长的编码方式。具体实现在ziplist.c和ziplist.h文件中。</p><blockquote><p>Redis3.2版本前，ziplist与adlist作为列表对象的底层实现，而3.2后列表对象的底层实现改为quciklist，而ziplist则在quicklist中被应用，除了quicklist，ziplist还被用于zset的底层实现。</p></blockquote><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><h3 id="列表构成"><a href="#列表构成" class="headerlink" title="列表构成"></a>列表构成</h3><figure class="highlight c"><table><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="meta">#<span class="meta-keyword">define</span> ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIPLIST_END_SIZE        (sizeof(uint8_t))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIP_END 255</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">char</span> *<span class="title">ziplistNew</span><span class="params">(<span class="keyword">void</span>)</span> </span>&#123;</span><br><span class="line">    <span class="comment">/* 内存申请 */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> *zl = zmalloc(bytes);</span><br><span class="line">    <span class="comment">/* 默认值赋值 */</span></span><br><span class="line">    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);</span><br><span class="line">    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);</span><br><span class="line">    ZIPLIST_LENGTH(zl) = <span class="number">0</span>;</span><br><span class="line">    zl[bytes<span class="number">-1</span>] = ZIP_END;</span><br><span class="line">    <span class="keyword">return</span> zl;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过初始化函数<code>ziplistNew</code>，可知压缩列表本质上是一个字节数组。初始化ziplist过程，一共申请了11字节的内存空间，其中包括两个uint32_t，一个uint16_t，一个uint8_t。初始化后压缩列表内部内存分布如下</p><p><img src="/images/ziplist.png" alt=""></p><p>组成部分含义：</p><ul><li>zlbytes，表示整个压缩列表占用字节数，包括其自身4字节</li><li>zltail，表示到最后一位节点的偏移量，从而能O(1)复杂度获取末尾节点</li><li>zllen，表示压缩列表中节点个数。</li><li>entry，压缩列表中的每个节点，其中节点长度由保存内容定义</li><li>zlend，用于标记压缩列表结尾，使用特殊值255</li></ul><p>其中，因为zllen只有两个字节，当压缩列表中节点数大于65534（2^16 - 2）时，都记为65535，而节点的真实数量需要遍历整个压缩列表，此时获取元素个数的时间复杂度从O(1)上升为O(n)。</p><h3 id="列表节点构成"><a href="#列表节点构成" class="headerlink" title="列表节点构成"></a>列表节点构成</h3><p>列表节点entry，其中逻辑结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;prevlen&gt; &lt;encoding&gt; &lt;content&gt;</span><br></pre></td></tr></table></figure><h3 id="prevlen"><a href="#prevlen" class="headerlink" title="prevlen"></a>prevlen</h3><p>prevlen表示前节点的节点长度，方便反向遍历。根据前一个节点的长度，分为两种情况：</p><ul><li>前节点长度小于254时，占用1字节，用来表示前节点长度</li><li>前节点长度大于等于254时，占用5字节，其中第1个字节为特殊值0xFE(254)，后面4字节用来表示实际长度</li></ul><p>如下源码所示，判断节点的第1个字节，如果其小于254，则该值为前节点长度，否则后4字节的值，为前节点长度。</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIP_BIG_PREVLEN 254</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* prevlen内存占用字节数 */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do &#123;                          \</span></span><br><span class="line">    <span class="keyword">if</span> ((ptr)[<span class="number">0</span>] &lt; ZIP_BIG_PREVLEN) &#123;                                          \</span><br><span class="line">        (prevlensize) = <span class="number">1</span>;                                                     \</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;                                                                   \</span><br><span class="line">        (prevlensize) = <span class="number">5</span>;                                                     \</span><br><span class="line">    &#125;                                                                          \</span><br><span class="line">&#125; <span class="keyword">while</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 前节点内存占用 */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do &#123;                     \</span></span><br><span class="line">    ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \</span><br><span class="line">    <span class="keyword">if</span> ((prevlensize) == <span class="number">1</span>) &#123;                                                  \</span><br><span class="line">        (prevlen) = (ptr)[<span class="number">0</span>];                                                  \</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((prevlensize) == <span class="number">5</span>) &#123;                                           \</span><br><span class="line">        assert(<span class="keyword">sizeof</span>((prevlen)) == <span class="number">4</span>);                                        \</span><br><span class="line">        <span class="built_in">memcpy</span>(&amp;(prevlen), ((<span class="keyword">char</span>*)(ptr)) + <span class="number">1</span>, <span class="number">4</span>);                             \</span><br><span class="line">        memrev32ifbe(&amp;prevlen);                                                \</span><br><span class="line">    &#125;                                                                          \</span><br><span class="line">&#125; <span class="keyword">while</span>(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h3 id="content"><a href="#content" class="headerlink" title="content"></a>content</h3><p>节点的content负责保存节点的值，节点值可以是一个字节数组或者整数，值的类型和长度由节点的encoding属性决定</p><h3 id="encoding"><a href="#encoding" class="headerlink" title="encoding"></a>encoding</h3><p>encoding表示当前节点的数据类型以及长度，其中整数节点，encoding均为1字节，而字符串根据长度不同，可能为1，2，5字节。具体的encoding值与数据类型对照表如下</p><table><thead><tr><th>字节值</th><th>占用字节</th><th>数据长度</th><th>特别说明</th></tr></thead><tbody><tr><td>11000000</td><td>1</td><td>int16_t类型的整数</td><td></td></tr><tr><td>11010000</td><td>1</td><td>int32_t类型的整数</td><td></td></tr><tr><td>11100000</td><td>1</td><td>int64_t类型的整数</td><td></td></tr><tr><td>11110000</td><td>1</td><td>24位有符号整数</td><td></td></tr><tr><td>11111110</td><td>1</td><td>8位有符号整数</td><td></td></tr><tr><td>1111xxxx</td><td>1</td><td>0-12的无符号整数</td><td>数据存储在encoding部分的后4位，使用0001-1101表示0-12</td></tr><tr><td>00xxxxxx</td><td>1</td><td>&lt;=6bits的字节数组</td><td>前两位表示类型，后6位为数据长度</td></tr><tr><td>01xxxxxx+&lt;1 bytes&gt;</td><td>2</td><td>&lt;=14bits的字节数组</td><td>前两位表示类型，后14位为数据长度</td></tr><tr><td>10000000+&lt;4 bytes&gt;</td><td>5</td><td>&lt;=32bits的字节数组</td><td>前两位表示类型，接着6位留空，后32位为数据长度</td></tr></tbody></table><h3 id="zlentry"><a href="#zlentry" class="headerlink" title="zlentry"></a>zlentry</h3><p>因为压缩列表本质只是字节数组，为了方便操作，redis还定义了zlentry结构体，操作时，将每个节点包含信息按照一定规则写入zlentry中。</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zlentry</span> &#123;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> prevrawlensize; <span class="comment">/* Bytes used to encode the previous entry len*/</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> prevrawlen;     <span class="comment">/* Previous entry len. */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> lensize;        <span class="comment">/* Bytes used to encode this entry type/len.</span></span><br><span class="line"><span class="comment">                                    For example strings have a 1, 2 or 5 bytes</span></span><br><span class="line"><span class="comment">                                    header. Integers always use a single byte.*/</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> len;            <span class="comment">/* Bytes used to represent the actual entry.</span></span><br><span class="line"><span class="comment">                                    For strings this is just the string length</span></span><br><span class="line"><span class="comment">                                    while for integers it is 1, 2, 3, 4, 8 or</span></span><br><span class="line"><span class="comment">                                    0 (for 4 bit immediate) depending on the</span></span><br><span class="line"><span class="comment">                                    number range. */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> headersize;     <span class="comment">/* prevrawlensize + lensize. */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> encoding;      <span class="comment">/* Set to ZIP_STR_* or ZIP_INT_* depending on</span></span><br><span class="line"><span class="comment">                                    the entry encoding. However for 4 bits</span></span><br><span class="line"><span class="comment">                                    immediate integers this can assume a range</span></span><br><span class="line"><span class="comment">                                    of values and must be range-checked. */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> *p;            <span class="comment">/* Pointer to the very start of the entry, that</span></span><br><span class="line"><span class="comment">                                    is, this points to prev-entry-len field. */</span></span><br><span class="line">&#125; zlentry;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 将包含节点信息的字节数组属性 写入zlentry结构体中 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">zipEntry</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">char</span> *p, zlentry *e)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    ZIP_DECODE_PREVLEN(p, e-&gt;prevrawlensize, e-&gt;prevrawlen);</span><br><span class="line">    ZIP_DECODE_LENGTH(p + e-&gt;prevrawlensize, e-&gt;encoding, e-&gt;lensize, e-&gt;len);</span><br><span class="line">    e-&gt;headersize = e-&gt;prevrawlensize + e-&gt;lensize;</span><br><span class="line">    e-&gt;p = p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="连锁更新"><a href="#连锁更新" class="headerlink" title="连锁更新"></a>连锁更新</h2><p>每个节点会存在prevlen属性，用来记录前置节点的长度，根据前置节点长度还分为两种情况，当长度大于等于254时，占用空间会从1字节扩大到5字节。所谓连锁更新，就是多个长度处于250字节~253字节之间的连续节点，当一个节点更新后，导致下一个节点prevlen由1字节变为5字节，从而导致下下一个节点prevlen值增大，产生连锁反应。因为连锁更新在最坏情况下需要对压缩列表执行N次空间预分配，而每次空间预分配的最坏复杂度为O(n)，所以连锁更新的最坏复杂度为O(n^2)。不过，虽然连锁更新的复杂度高，但出现的几率较低，需要多个特殊长度的节点，所以插入删除命令的平均复杂度均为O(n)，最坏复杂度为O(n^2)。<br>以下是连锁更新的部分源码:</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> *__ziplistCascadeUpdate(<span class="keyword">unsigned</span> <span class="keyword">char</span> *zl, <span class="keyword">unsigned</span> <span class="keyword">char</span> *p) &#123;</span><br><span class="line">    <span class="keyword">size_t</span> curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;</span><br><span class="line">    <span class="keyword">size_t</span> offset, noffset, extra;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> *np;</span><br><span class="line">    zlentry cur, next;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 是否到末尾 */</span></span><br><span class="line">    <span class="keyword">while</span> (p[<span class="number">0</span>] != ZIP_END) &#123;</span><br><span class="line">        <span class="comment">/* 解析当前节点信息 */</span></span><br><span class="line">        zipEntry(p, &amp;cur);</span><br><span class="line">        <span class="comment">/* 当前节点长度 */</span></span><br><span class="line">        rawlen = cur.headersize + cur.len;</span><br><span class="line">        <span class="comment">/* 当前节点长度信息需要的长度 */</span></span><br><span class="line">        rawlensize = zipStorePrevEntryLength(<span class="literal">NULL</span>,rawlen);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 列表末尾 */</span></span><br><span class="line">        <span class="keyword">if</span> (p[rawlen] == ZIP_END) <span class="keyword">break</span>;</span><br><span class="line">        <span class="comment">/* 解析下一个节点信息 */</span></span><br><span class="line">        zipEntry(p+rawlen, &amp;next);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 下一个节点prevlen未发生更新 */</span></span><br><span class="line">        <span class="keyword">if</span> (next.prevrawlen == rawlen) <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* prevlen发生更新 */</span></span><br><span class="line">        <span class="keyword">if</span> (next.prevrawlensize &lt; rawlensize) &#123;</span><br><span class="line">            <span class="comment">/* 当前节点与ziplist的偏移量 */</span></span><br><span class="line">            offset = p-zl;</span><br><span class="line">            <span class="comment">/* 重新更大的内存空间 */</span></span><br><span class="line">            extra = rawlensize-next.prevrawlensize;</span><br><span class="line">            zl = ziplistResize(zl,curlen+extra);</span><br><span class="line">            <span class="comment">/* p新内存地址 */</span></span><br><span class="line">            p = zl+offset;</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 下一个节点 */</span></span><br><span class="line">            np = p+rawlen;</span><br><span class="line">            noffset = np-zl;</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 如果下一个节点还不是末尾节点，则更新ziplist的zltail属性*/</span></span><br><span class="line">            <span class="keyword">if</span> ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) &#123;</span><br><span class="line">                ZIPLIST_TAIL_OFFSET(zl) =</span><br><span class="line">                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 内存向后移动 */</span></span><br><span class="line">            memmove(np+rawlensize,</span><br><span class="line">                np+next.prevrawlensize,</span><br><span class="line">                curlen-noffset-next.prevrawlensize<span class="number">-1</span>);</span><br><span class="line">            <span class="comment">/* 记录新的prevlen */</span></span><br><span class="line">            zipStorePrevEntryLength(np,rawlen);</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 向下一个节点移动 */</span></span><br><span class="line">            p += rawlen;</span><br><span class="line">            curlen += extra;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">/* 如果下一个节点原本的prevlen字段可以容纳新节点长度信息，则直接写入 */</span></span><br><span class="line">            <span class="keyword">if</span> (next.prevrawlensize &gt; rawlensize) &#123;</span><br><span class="line">                zipStorePrevEntryLengthLarge(p+rawlen,rawlen);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                zipStorePrevEntryLength(p+rawlen,rawlen);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 更新结束，退出遍历 */</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> zl;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">zipStorePrevEntryLengthLarge</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">char</span> *p, <span class="keyword">unsigned</span> <span class="keyword">int</span> len)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (p != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        p[<span class="number">0</span>] = ZIP_BIG_PREVLEN;</span><br><span class="line">        <span class="built_in">memcpy</span>(p+<span class="number">1</span>,&amp;len,<span class="keyword">sizeof</span>(len));</span><br><span class="line">        memrev32ifbe(p+<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>+<span class="keyword">sizeof</span>(len);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">zipStorePrevEntryLength</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">char</span> *p, <span class="keyword">unsigned</span> <span class="keyword">int</span> len)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> (len &lt; ZIP_BIG_PREVLEN) ? <span class="number">1</span> : <span class="keyword">sizeof</span>(len)+<span class="number">1</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (len &lt; ZIP_BIG_PREVLEN) &#123;</span><br><span class="line">            p[<span class="number">0</span>] = len;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> zipStorePrevEntryLengthLarge(p,len);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>ziplist是Redis为了节省内存而自定义的一种特殊编码的顺序性数据结构，与双端链表adlist相比，具有以下特点：</p><ul><li>整个ziplist存在一个连续内存中</li><li>通过固定内存偏移量获取头节点，保留与尾节点的偏移量zltail，而不像双端链表存储头尾节点</li><li>每个节点保留前置节点的长度，从而实现反向遍历，而不像双端链表存储前置节点指针</li><li>每个节点采用变长的编码方式，存储字符串或整数值</li><li>在特定条件下，ziplist会发生连锁更新，导致增删操作时间复杂度由O(n)变为O(n^2)</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="http://redisbook.com/preview/ziplist/list.html" target="_blank" rel="noopener">Redis设计与实现</a></li><li><a href="https://juejin.im/post/6844904012819464206" target="_blank" rel="noopener">Redis源码解析-ziplist</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Ziplist是Redis中为了节约内存空间而实现的顺序性数据结构，通过一系列特殊编码，将数据与其编码信息存储在连续内存中，一个ziplist可以包含多个节点，每个节点可存储一个字节数组或一个整数值，其中对于节点值类型存储还采用了变长的编码方式。具体实现在ziplist.c
      
    
    </summary>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/categories/redis/"/>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/tags/redis/"/>
    
  </entry>
  
  <entry>
    <title>深入浅出Redis之adlist</title>
    <link href="https://www.wzmmmmj.com/2020/07/19/redis-adlist/"/>
    <id>https://www.wzmmmmj.com/2020/07/19/redis-adlist/</id>
    <published>2020-07-19T22:32:18.000Z</published>
    <updated>2022-04-05T08:05:52.186Z</updated>
    
    <content type="html"><![CDATA[<p>Adlist，全称A generic doubly linked list，其实就是Redis中自定义的双端链表，作为一个通用数据结构，主要作用于一些Redis的功能模块，例如RedisServer中关联多个client或者RedisClient中的输出缓冲区等。具体adlist源码实现在adlist.c和adlist.h文件中。</p><blockquote><p>Redis3.2版本前，列表的底层实现是由adlist和ziplist组成，后被quicklist取代，关于quicklist与ziplist后续会单独讲。</p></blockquote><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p>首先查看头文件adlist.h，对listNode的类型定义：</p><figure class="highlight c"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">prev</span>;</span>  <span class="comment">/* 前驱节点 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">next</span>;</span>  <span class="comment">/* 后驱节点 */</span></span><br><span class="line">    <span class="keyword">void</span> *value;  <span class="comment">/* 节点值 */</span></span><br><span class="line">&#125; listNode;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">list</span> &#123;</span></span><br><span class="line">    listNode *head;  <span class="comment">/* 链表头结点 */</span></span><br><span class="line">    listNode *tail;  <span class="comment">/* 链表尾结点 */</span></span><br><span class="line">    <span class="keyword">void</span> *(*dup)(<span class="keyword">void</span> *ptr);  <span class="comment">/* 自定义复制函数 */</span></span><br><span class="line">    <span class="keyword">void</span> (*<span class="built_in">free</span>)(<span class="keyword">void</span> *ptr);  <span class="comment">/* 自定义释放函数 */</span></span><br><span class="line">    <span class="keyword">int</span> (*match)(<span class="keyword">void</span> *ptr, <span class="keyword">void</span> *key);  <span class="comment">/* 自定义比较函数 */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> len;  <span class="comment">/* 链表长度 */</span></span><br><span class="line">&#125; <span class="built_in">list</span>;</span><br></pre></td></tr></table></figure><p>通过前驱节点prev与后驱节点next连接多个listNode，即可实现双端链表，不过adlist中还定义了结构体list，字段含义如下：</p><ul><li>head/tail：分别指向链表的头尾节点，从而更方便获取头尾节点进行操作</li><li>len：保存链表长度，使查询链表长度只需要O(1)时间复杂度</li></ul><p>对于表头节点的prev指针，以及表尾节点的next指针都会指向NULL，所以adlist还是一个无环链表。</p><p>listNode中使用void *指针保存节点值，对节点值的类型不做限制，从而实现多态链表。而对于不同类型的节点值，需要类型特定函数来处理某些操作，所以list中可自定义三类函数指针，这些函数指针也是通过void *进行保存，对函数类型不做限制。</p><ul><li>dup：自定义复制函数，未定义时，默认的复制方式是同享数据</li><li>free：自定义释放函数，对于一些复杂节点值，可能会需要额外释放内存，未定义时，只释放list本身</li><li>match：自定义比较函数，例如调用listSearchKey时，会调用该函数查看结果是否为true，未定义时，比较数据的指针值</li></ul><p>综上，Redis中adlist的结构如下图所示：</p><p><img src="/images/redis-adlist.png" alt="redis adlist"></p><h2 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h2><p>除了双端链表的基本结构外，adlist.h中还定义了双端链表的迭代器，对于链表遍历操作，都需要通过listIter实例进行，而list则专注于保存节点值。数据结构如下：</p><figure class="highlight c"><table><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"><span class="meta">#<span class="meta-keyword">define</span> AL_START_HEAD 0  <span class="comment">/* 从头开始 */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> AL_START_TAIL 1  <span class="comment">/* 从尾开始 */</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">listIter</span> &#123;</span></span><br><span class="line">    listNode *next; <span class="comment">/* 下一个节点指针 */</span></span><br><span class="line">    <span class="keyword">int</span> direction;  <span class="comment">/* 遍历方向 */</span></span><br><span class="line">&#125; listIter;</span><br></pre></td></tr></table></figure><p>其中，listIter对象根据变量direction，判断遍历方向。以下是listIter对象的创建及使用</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 根据传参返回链表迭代器 */</span></span><br><span class="line"><span class="function">listIter *<span class="title">listGetIterator</span><span class="params">(<span class="built_in">list</span> *<span class="built_in">list</span>, <span class="keyword">int</span> direction)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    listIter *iter;</span><br><span class="line">    <span class="comment">/* 分配内存 */</span></span><br><span class="line">    <span class="keyword">if</span> ((iter = zmalloc(<span class="keyword">sizeof</span>(*iter))) == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">/* 根据传参direction，判断遍历方向  */</span></span><br><span class="line">    <span class="keyword">if</span> (direction == AL_START_HEAD)</span><br><span class="line">        iter-&gt;next = <span class="built_in">list</span>-&gt;head;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        iter-&gt;next = <span class="built_in">list</span>-&gt;tail;</span><br><span class="line">    iter-&gt;direction = direction;</span><br><span class="line">    <span class="keyword">return</span> iter;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 获取下一个节点 */</span></span><br><span class="line"><span class="function">listNode *<span class="title">listNext</span><span class="params">(listIter *iter)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    listNode *current = iter-&gt;next;</span><br><span class="line">    <span class="comment">/* 判断是否到头 or 尾节点 */</span></span><br><span class="line">    <span class="keyword">if</span> (current != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="comment">/* 判断遍历方向 */</span></span><br><span class="line">        <span class="keyword">if</span> (iter-&gt;direction == AL_START_HEAD)</span><br><span class="line">            iter-&gt;next = current-&gt;next;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            iter-&gt;next = current-&gt;prev;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> current;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="adlist的一些操作"><a href="#adlist的一些操作" class="headerlink" title="adlist的一些操作"></a>adlist的一些操作</h2><p>Redis中adlist的创建，添加节点无异于其他双端链表，感兴趣可以自行查看。这里列举一些与dup/free/match函数相关的api源码，加深对这三类函数指针的理解。</p><h3 id="清空"><a href="#清空" class="headerlink" title="清空"></a>清空</h3><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 清空链表所有节点内存 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">listEmpty</span><span class="params">(<span class="built_in">list</span> *<span class="built_in">list</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> len;</span><br><span class="line">    listNode *current, *next;</span><br><span class="line"></span><br><span class="line">    current = <span class="built_in">list</span>-&gt;head;</span><br><span class="line">    len = <span class="built_in">list</span>-&gt;len;</span><br><span class="line">    <span class="comment">/* 递归释放节点值 */</span></span><br><span class="line">    <span class="keyword">while</span>(len--) &#123;</span><br><span class="line">        next = current-&gt;next;</span><br><span class="line">        <span class="comment">/* 判断free是否定义，如果定义，则传入调用free，释放节点值内存 */</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">list</span>-&gt;<span class="built_in">free</span>) <span class="built_in">list</span>-&gt;<span class="built_in">free</span>(current-&gt;value);</span><br><span class="line">        <span class="comment">/* 释放节点本身内存 */</span></span><br><span class="line">        zfree(current);</span><br><span class="line">        current = next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 清空链表节点 */</span></span><br><span class="line">    <span class="built_in">list</span>-&gt;head = <span class="built_in">list</span>-&gt;tail = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="built_in">list</span>-&gt;len = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 先调用listEmpty释放每个节点的内存，在调用zfree释放list本身内存 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">listRelease</span><span class="params">(<span class="built_in">list</span> *<span class="built_in">list</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    listEmpty(<span class="built_in">list</span>);</span><br><span class="line">    zfree(<span class="built_in">list</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="复制"><a href="#复制" class="headerlink" title="复制"></a>复制</h3><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 复制一个相同的list */</span></span><br><span class="line"><span class="function"><span class="built_in">list</span> *<span class="title">listDup</span><span class="params">(<span class="built_in">list</span> *orig)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">list</span> *copy;</span><br><span class="line">    listIter iter;</span><br><span class="line">    listNode *node;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 新建list，将dup/free/match函数赋值 */</span></span><br><span class="line">    <span class="keyword">if</span> ((copy = listCreate()) == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    copy-&gt;dup = orig-&gt;dup;</span><br><span class="line">    copy-&gt;<span class="built_in">free</span> = orig-&gt;<span class="built_in">free</span>;</span><br><span class="line">    copy-&gt;match = orig-&gt;match;</span><br><span class="line">    <span class="comment">/* 初始化迭代器 */</span></span><br><span class="line">    listRewind(orig, &amp;iter);</span><br><span class="line">    <span class="comment">/* 递归迭代器，主要是复制节点值 */</span></span><br><span class="line">    <span class="keyword">while</span>((node = listNext(&amp;iter)) != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="keyword">void</span> *value;</span><br><span class="line">        <span class="comment">/* 判断dup函数是否定义 */</span></span><br><span class="line">        <span class="keyword">if</span> (copy-&gt;dup) &#123;</span><br><span class="line">            <span class="comment">/* 如果有，则通过dup进行赋值节点值 */</span></span><br><span class="line">            value = copy-&gt;dup(node-&gt;value);</span><br><span class="line">            <span class="keyword">if</span> (value == <span class="literal">NULL</span>) &#123;  <span class="comment">/* 如果失败则释放内存，并直接return */</span></span><br><span class="line">                listRelease(copy);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span></span><br><span class="line">            <span class="comment">/* 未定义dup函数，则共享节点值指针 */</span></span><br><span class="line">            value = node-&gt;value;</span><br><span class="line">        <span class="comment">/* 将节点添加到链表尾部，如果失败则释放内存，并直接return */</span></span><br><span class="line">        <span class="keyword">if</span> (listAddNodeTail(copy, value) == <span class="literal">NULL</span>) &#123;</span><br><span class="line">            listRelease(copy);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> copy;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h3><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 链表中查看是否存在与key相同的值 */</span></span><br><span class="line"><span class="function">listNode *<span class="title">listSearchKey</span><span class="params">(<span class="built_in">list</span> *<span class="built_in">list</span>, <span class="keyword">void</span> *key)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    listIter iter;</span><br><span class="line">    listNode *node;</span><br><span class="line">    <span class="comment">/* 初始化迭代器 */</span></span><br><span class="line">    listRewind(<span class="built_in">list</span>, &amp;iter);</span><br><span class="line">    <span class="comment">/* 递归迭代器，主要是比较节点值与key，如果相同则直接return */</span></span><br><span class="line">    <span class="keyword">while</span>((node = listNext(&amp;iter)) != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="comment">/* 判断是否定义match函数 */</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">list</span>-&gt;match) &#123;</span><br><span class="line">            <span class="comment">/* 如果有，则通过match，比较节点值与key是否"相同" */</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">list</span>-&gt;match(node-&gt;value, key)) &#123;</span><br><span class="line">                <span class="keyword">return</span> node;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">/* 未定义，则直接比较节点值与key是否相同 */</span></span><br><span class="line">            <span class="keyword">if</span> (key == node-&gt;value) &#123;</span><br><span class="line">                <span class="keyword">return</span> node;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 未找到则返回NULL */</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>Redis中实现的双端链表adlist，相同于基础的双端链表，具有以下几个特点：</p><ol><li>节点值为void *类型，可为任意类型;</li><li>list结构中dup/free/match属性，也为void *类型，可以为链表设置类型特定函数，从而处理不同类型的节点值;</li><li>list结构中head/tail属性，使得获取头尾节点以只需要O(1)的时间复杂度，并且可以正序/倒序访问;</li><li>list结构中len属性，使得获取链表长度只需要O(1)的时间复杂度；</li><li>链表表头节点的prev指针与表尾节点next都指向NULL，从而adlist为无环链表;</li><li>链表的迭代操作由迭代器listIter对象完成。</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://redisbook.readthedocs.io/en/latest/internal-datastruct/adlist.html" target="_blank" rel="noopener">Redis设计与实现</a></li><li><a href="http://czrzchao.com/redisSourceAdlist" target="_blank" rel="noopener">Redis源码解读之ADLIST</a></li><li><a href="https://www.ibm.com/developerworks/cn/linux/l-cn-cpolym/index.html" target="_blank" rel="noopener">C语言实现多态性</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Adlist，全称A generic doubly linked list，其实就是Redis中自定义的双端链表，作为一个通用数据结构，主要作用于一些Redis的功能模块，例如RedisServer中关联多个client或者RedisClient中的输出缓冲区等。具体adl
      
    
    </summary>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/categories/redis/"/>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/tags/redis/"/>
    
  </entry>
  
  <entry>
    <title>深入浅出Redis之sds</title>
    <link href="https://www.wzmmmmj.com/2020/07/12/redis-sds/"/>
    <id>https://www.wzmmmmj.com/2020/07/12/redis-sds/</id>
    <published>2020-07-12T23:25:24.000Z</published>
    <updated>2022-04-05T08:05:52.186Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近在看《Redis的设计与实现》，感觉书还不错，但由于书中讲解的Redis版本较低，有些地方与现版本有出入，所以通过简单浏览源码，加深理解。本文代码使用<a href="https://github.com/redis/redis/tree/6.0" target="_blank" rel="noopener">Redis6.0</a></p></blockquote><p>SDS，全称Simple Dynamic String，作为Redis中的字符串表示，主要作用于Redis中字符串对象StringObject的底层实现，以及Redis内部作为char*类型的替代品。具体sds源码实现在sds.c和sds.h文件中。</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p>查看sds.h文件，不难发现对sds的类型定义，其实就是char *的一个alias。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">char</span> *sds;</span><br></pre></td></tr></table></figure><p>尽管C语言字符串本质上也是char *，不过与之不同的是，一个sds的完整结构，分为两块部分，sdshdr结构和字符数组。以下为sdshdr结构体：</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr5</span> &#123;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags; <span class="comment">/* 3 lsb of type, and 5 msb of string length */</span></span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr8</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint8_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="keyword">uint8_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr16</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint16_t</span> len;</span><br><span class="line">    <span class="keyword">uint16_t</span> alloc;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags;</span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr32</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint32_t</span> len;</span><br><span class="line">    <span class="keyword">uint32_t</span> alloc;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags;</span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr64</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint64_t</span> len;</span><br><span class="line">    <span class="keyword">uint64_t</span> alloc;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags;</span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* flags常量 */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_5  0</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_8  1</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_16 2</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_32 3</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_64 4</span></span><br></pre></td></tr></table></figure><p>sds针对不同的字符串长度，为了节省内存，定义了五种类型的header，其中sdshdr5较为特殊，与其他header结构不同，它并没有alloc和len字段，所以flags字段被分为两部分，低三位作为header类型，高五位记录字符串长度。因为不具备alloc字段，所以sdshdr5不会预分配空间，如果字符串动态增加，则需要重新分配内存，大部分场景不太会使用sdshdr5。其余4个header的结构类似，字段含义如下</p><ul><li>len： 字符串真实长度</li><li>alloc：字符串当前可使用的最大容量</li><li>flags：占一字节，前三位表示header类型</li></ul><p>其中结构体中，有几个特殊的点</p><ul><li><code>__attribute__ ((__packed__))</code>表示编译器不需要对结构体进行内存对齐，从而使字段内存连续</li><li><code>char buf[]</code>，未指定长度且定义在结构体的最后一个字段，是一个柔性数组，表示flags字段后是一个字符数组，而柔性数组buf[]只作为一个符号地址存在，通过sizeof 返回的结构大小不包括柔性数组的内存大小</li></ul><blockquote><p>对内存对齐与柔性数组的详细解释：<a href="https://www.zhihu.com/question/27862634" target="_blank" rel="noopener">内存对齐</a>与<a href="https://en.wikipedia.org/wiki/Flexible_array_member" target="_blank" rel="noopener">柔性数组</a></p></blockquote><p>通过源码中定义的宏以及sdslen方法不难看出这两点</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_MASK 7</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_BITS 3</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_TYPE_5_LEN(f) ((f)&gt;&gt;SDS_TYPE_BITS)</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">size_t</span> <span class="title">sdslen</span><span class="params">(<span class="keyword">const</span> sds s)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags = s[<span class="number">-1</span>];</span><br><span class="line">    <span class="keyword">switch</span>(flags&amp;SDS_TYPE_MASK) &#123;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_5:</span><br><span class="line">            <span class="keyword">return</span> SDS_TYPE_5_LEN(flags);  <span class="comment">/* sdshdr5 len保存在flags的高五位中*/</span></span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_8:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">8</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_16:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">16</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_32:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">32</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_64:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">64</span>,s)-&gt;len;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>”##“ 连接符，预处理运算符，例如SDS_HDR(8,s)宏展开后是((struct sdshdr8 *)((s)-(sizeof(struct sdshdr8))))</p></blockquote><p>其中因为buf[]是柔性数组，sizeof(struct sdshdr8)不会包含数组大小，SDS_HDR即表示字符串向前偏移结构体的大小，从而获得header起始位置的指针。而flags则可以通过s向前偏移一个字节获得，即<code>flags=s[-1]</code></p><p>综上，不难看出sds结构体中的header部分，实际存储在字符数组之前，如下图所示</p><figure class="highlight plain"><table><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">+---------------------+-------------------------------+-----------+</span><br><span class="line">| len | alloc | flags | Binary safe C alike string... | Null term |</span><br><span class="line">+---------------------+-------------------------------+-----------+</span><br><span class="line">          |           |</span><br><span class="line">          &#96;-&gt;Header   &#96;-&gt; Pointer returned to the user.</span><br></pre></td></tr></table></figure><h2 id="SDS的一些操作"><a href="#SDS的一些操作" class="headerlink" title="SDS的一些操作"></a>SDS的一些操作</h2><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 通过sdsnew函数为字符数组创建一个sds */</span></span><br><span class="line"><span class="function">sds <span class="title">sdsnew</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *init)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">size_t</span> initlen = (init == <span class="literal">NULL</span>) ? <span class="number">0</span> : <span class="built_in">strlen</span>(init);</span><br><span class="line">    <span class="keyword">return</span> sdsnewlen(init, initlen);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">sds <span class="title">sdsnewlen</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span> *init, <span class="keyword">size_t</span> initlen)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *sh;</span><br><span class="line">    sds s;</span><br><span class="line">    <span class="keyword">char</span> type = sdsReqType(initlen);  <span class="comment">/* 根据所给字符串长度给出适合的sds类型 */</span></span><br><span class="line">    <span class="comment">/* 长度为0时，由于可能会进行追加操作，而SDS_TYPE_5不适合追加数据，所以sds类型为SDS_TYPE_8*/</span></span><br><span class="line">    <span class="keyword">if</span> (type == SDS_TYPE_5 &amp;&amp; initlen == <span class="number">0</span>) type = SDS_TYPE_8;</span><br><span class="line">    <span class="keyword">int</span> hdrlen = sdsHdrSize(type); <span class="comment">/* 根据sds类型返回对应的sdshdr大小 */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> *fp; <span class="comment">/* flags pointer. */</span></span><br><span class="line"></span><br><span class="line">    sh = s_malloc(hdrlen+initlen+<span class="number">1</span>); <span class="comment">/* 申请内存，需要为终止符\0额外申请1字节*/</span></span><br><span class="line">    <span class="keyword">if</span> (sh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (init==SDS_NOINIT)</span><br><span class="line">        init = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!init)</span><br><span class="line">        <span class="built_in">memset</span>(sh, <span class="number">0</span>, hdrlen+initlen+<span class="number">1</span>); <span class="comment">/* 分配内存 */</span></span><br><span class="line">    s = (<span class="keyword">char</span>*)sh+hdrlen; <span class="comment">/* 内存向右偏移sdshdr大小，获取数组指针 */</span></span><br><span class="line">    fp = ((<span class="keyword">unsigned</span> <span class="keyword">char</span>*)s)<span class="number">-1</span>; <span class="comment">/* 数组指针向左偏移一位，获取flags指针 */</span></span><br><span class="line">    <span class="keyword">switch</span>(type) &#123;  <span class="comment">/* 根据sds类型，设置sdshdr alloc len flags 值 */</span></span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_5: &#123;</span><br><span class="line">            *fp = type | (initlen &lt;&lt; SDS_TYPE_BITS);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_8: &#123;</span><br><span class="line">            SDS_HDR_VAR(<span class="number">8</span>,s);</span><br><span class="line">            sh-&gt;len = initlen;</span><br><span class="line">            sh-&gt;alloc = initlen;</span><br><span class="line">            *fp = type;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (initlen &amp;&amp; init)</span><br><span class="line">        <span class="built_in">memcpy</span>(s, init, initlen);</span><br><span class="line">    s[initlen] = <span class="string">'\0'</span>;  <span class="comment">/* 字符结尾设置\0终止符 */</span></span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="扩容-amp-空间预分配"><a href="#扩容-amp-空间预分配" class="headerlink" title="扩容&amp;空间预分配"></a>扩容&amp;空间预分配</h3><p>大多数追加操作中，都会调用到扩容，其中根据新长度的大小，扩容大小也不同，一般为新长度的两倍，在大小为1Mb后，每次扩容变为增加1Mb，从而实现内存预分配内存</p><figure class="highlight c"><table><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SDS_MAX_PREALLOC (1024*1024)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (newlen &lt; SDS_MAX_PREALLOC)</span><br><span class="line">    newlen *= <span class="number">2</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    newlen += SDS_MAX_PREALLOC;</span><br></pre></td></tr></table></figure><p>完整代码如下</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">sds <span class="title">sdsMakeRoomFor</span><span class="params">(sds s, <span class="keyword">size_t</span> addlen)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *sh, *newsh;</span><br><span class="line">    <span class="keyword">size_t</span> avail = sdsavail(s);  <span class="comment">/* sds剩余可用 即s.alloc - s.len */</span></span><br><span class="line">    <span class="keyword">size_t</span> len, newlen;</span><br><span class="line">    <span class="keyword">char</span> type, oldtype = s[<span class="number">-1</span>] &amp; SDS_TYPE_MASK;</span><br><span class="line">    <span class="keyword">int</span> hdrlen;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 有足够空间则返回 */</span></span><br><span class="line">    <span class="keyword">if</span> (avail &gt;= addlen) <span class="keyword">return</span> s;</span><br><span class="line"></span><br><span class="line">    len = sdslen(s); <span class="comment">/* 字符串长度 */</span></span><br><span class="line">    sh = (<span class="keyword">char</span>*)s-sdsHdrSize(oldtype);  <span class="comment">/* sdshdr指针 */</span></span><br><span class="line">    newlen = (len+addlen);  <span class="comment">/* 新长度 */</span></span><br><span class="line">    <span class="keyword">if</span> (newlen &lt; SDS_MAX_PREALLOC)</span><br><span class="line">        newlen *= <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        newlen += SDS_MAX_PREALLOC;</span><br><span class="line"></span><br><span class="line">    type = sdsReqType(newlen); <span class="comment">/* 新长度合适的sds类型 */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 扩容时一般不为SDS_TYPE_5 */</span></span><br><span class="line">    <span class="keyword">if</span> (type == SDS_TYPE_5) type = SDS_TYPE_8;</span><br><span class="line"></span><br><span class="line">    hdrlen = sdsHdrSize(type); <span class="comment">/* 新sdshdr大小 */</span></span><br><span class="line">    <span class="keyword">if</span> (oldtype==type) &#123;</span><br><span class="line">        newsh = s_realloc(sh, hdrlen+newlen+<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        s = (<span class="keyword">char</span>*)newsh+hdrlen;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">/* 分配内存，拷贝原字符串，释放原sds内存，并设置好flags以及len */</span></span><br><span class="line">        newsh = s_malloc(hdrlen+newlen+<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        <span class="built_in">memcpy</span>((<span class="keyword">char</span>*)newsh+hdrlen, s, len+<span class="number">1</span>);</span><br><span class="line">        s_free(sh);</span><br><span class="line">        s = (<span class="keyword">char</span>*)newsh+hdrlen;</span><br><span class="line">        s[<span class="number">-1</span>] = type;</span><br><span class="line">        sdssetlen(s, len);</span><br><span class="line">    &#125;</span><br><span class="line">    sdssetalloc(s, newlen);</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="缩容-amp-惰性空间释放"><a href="#缩容-amp-惰性空间释放" class="headerlink" title="缩容&amp;惰性空间释放"></a>缩容&amp;惰性空间释放</h3><p>当某些操作后，减少了原有字符串大小，sds并不会立即重新释放空间，重新分配内存，而是继续保留那么多空间，说不定下次就用上了。例如<code>sdsclear</code>操作</p><figure class="highlight c"><table><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"><span class="comment">// 将len字段设置为0，但内存空间不释放。</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">sdsclear</span><span class="params">(sds s)</span> </span>&#123;</span><br><span class="line">    sdssetlen(s, <span class="number">0</span>);</span><br><span class="line">    s[<span class="number">0</span>] = <span class="string">'\0'</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然必要的时候也可以调用<code>sdsRemoveFreeSpace</code>，主动缩容</p><figure class="highlight c"><table><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">sds <span class="title">sdsRemoveFreeSpace</span><span class="params">(sds s)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *sh, *newsh;</span><br><span class="line">    <span class="keyword">char</span> type, oldtype = s[<span class="number">-1</span>] &amp; SDS_TYPE_MASK;</span><br><span class="line">    <span class="keyword">int</span> hdrlen, oldhdrlen = sdsHdrSize(oldtype);</span><br><span class="line">    <span class="keyword">size_t</span> len = sdslen(s);</span><br><span class="line">    <span class="keyword">size_t</span> avail = sdsavail(s);</span><br><span class="line">    sh = (<span class="keyword">char</span>*)s-oldhdrlen;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 可用空间为0，说明没必要缩容 */</span></span><br><span class="line">    <span class="keyword">if</span> (avail == <span class="number">0</span>) <span class="keyword">return</span> s;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 通过比较sdshdrd大小*/</span></span><br><span class="line">    type = sdsReqType(len);</span><br><span class="line">    hdrlen = sdsHdrSize(type);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* If the type is the same, or at least a large enough type is still</span></span><br><span class="line"><span class="comment">     * required, we just realloc(), letting the allocator to do the copy</span></span><br><span class="line"><span class="comment">     * only if really needed. Otherwise if the change is huge, we manually</span></span><br><span class="line"><span class="comment">     * reallocate the string to use the different header type. */</span></span><br><span class="line">    <span class="keyword">if</span> (oldtype==type || type &gt; SDS_TYPE_8) &#123;</span><br><span class="line">        newsh = s_realloc(sh, oldhdrlen+len+<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        s = (<span class="keyword">char</span>*)newsh+oldhdrlen;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; <span class="comment">/* 空余空间较大，直接重新分配内存 */</span></span><br><span class="line">        newsh = s_malloc(hdrlen+len+<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">        <span class="built_in">memcpy</span>((<span class="keyword">char</span>*)newsh+hdrlen, s, len+<span class="number">1</span>);</span><br><span class="line">        s_free(sh);</span><br><span class="line">        s = (<span class="keyword">char</span>*)newsh+hdrlen;</span><br><span class="line">        s[<span class="number">-1</span>] = type;</span><br><span class="line">        sdssetlen(s, len);</span><br><span class="line">    &#125;</span><br><span class="line">    sdssetalloc(s, len);</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>sds相比于C语言字符串，具有以下几个显著的特点：</p><ol><li>结构中存储字符串长度，获取字符长度只需要常量时间复杂度，而原生字符串则需要遍历数组；</li><li>二进制安全，可以存储字节数据，因为存储字符串长度，不会提前遇到\0字符而终止；</li><li>杜绝缓冲区溢出，C语言字符串本身不记录数组长度，增加操作时，分配内存不足时容易造成缓冲区溢出，而sds因为存在alloc，会在修改时，检查空间大小是否满足；</li><li>内存预分配以及惰性删除，减少内存重新分配次数；</li><li>兼容C标准库中的部分字符串函数。</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://github.com/antirez/sds" target="_blank" rel="noopener">Redis sds</a></li><li><a href="https://redisbook.readthedocs.io/en/latest/internal-datastruct/sds.html" target="_blank" rel="noopener">Redis设计与实现</a></li><li><a href="http://zhangtielei.com/posts/blog-redis-sds.html" target="_blank" rel="noopener">Redis内部数据结构详解——sds</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;最近在看《Redis的设计与实现》，感觉书还不错，但由于书中讲解的Redis版本较低，有些地方与现版本有出入，所以通过简单浏览源码，加深理解。本文代码使用&lt;a href=&quot;https://github.com/redis/redis/tree/6.
      
    
    </summary>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/categories/redis/"/>
    
    
      <category term="redis" scheme="https://www.wzmmmmj.com/tags/redis/"/>
    
  </entry>
  
  <entry>
    <title>Vim中的寄存器：Register</title>
    <link href="https://www.wzmmmmj.com/2020/05/18/vim-register/"/>
    <id>https://www.wzmmmmj.com/2020/05/18/vim-register/</id>
    <published>2020-05-18T00:27:57.000Z</published>
    <updated>2022-04-05T08:05:52.188Z</updated>
    
    <content type="html"><![CDATA[<p>刚接触vim时，相信多多少少都会遇到关于复制粘贴的问题，例如以下两个问题</p><ol><li>Vim编辑的内容如何复制到其他地方？</li><li>Vim中如何复制(y)一段内容后，多次复用，而不会被替换内容覆盖？</li></ol><p>其实这些问题都可能跟Vim中的寄存器有关，寄存器可以想象成内存中存储某些文本的空间，类似于粘贴板。在使用<code>y</code>，<code>p</code>，<code>d</code>等命令时，都会不经意间的操作Vim的寄存器。</p><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><p>Normal模式下，使用<code>&quot;{register}{command}</code>，  例如<code>&quot;add</code>，表示删除当前行内容，并将内容保存至<code>&quot;a</code>寄存器中，而使用<code>&quot;ap</code>，则可以粘贴<code>&quot;a</code>寄存器中的内容。而插入模式下，使用<code>ctrl-r a</code>来表示粘贴<code>&quot;a</code>寄存器中的内容。</p><p>Vim中输入<code>:register</code>，会输出使用中的寄存器以及其保存的值，或者<code>:register x</code>指定输出某些寄存器。</p><h2 id="寄存器类型"><a href="#寄存器类型" class="headerlink" title="寄存器类型"></a>寄存器类型</h2><p>Vim中输入 <code>:help registers</code>，其中介绍了vim中10种类型的寄存器</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">There are ten types of registers:*registers* *&#123;register&#125;* *E354*</span><br><span class="line">1. The unnamed register &quot;&quot;</span><br><span class="line">2. 10 numbered registers &quot;0 to &quot;9</span><br><span class="line">3. The small delete register &quot;-</span><br><span class="line">4. 26 named registers &quot;a to &quot;z or &quot;A to &quot;Z</span><br><span class="line">5. Three read-only registers &quot;:, &quot;., &quot;%</span><br><span class="line">6. Alternate buffer register &quot;#</span><br><span class="line">7. The expression register &quot;&#x3D;</span><br><span class="line">8. The selection and drop registers &quot;*, &quot;+ and &quot;~</span><br><span class="line">9. The black hole register &quot;_</span><br><span class="line">10. Last search pattern register &quot;&#x2F;</span><br></pre></td></tr></table></figure><h4 id="匿名寄存器"><a href="#匿名寄存器" class="headerlink" title="匿名寄存器"></a>匿名寄存器</h4><p>当使用<code>d</code>，<code>c</code>，<code>s</code>，<code>x</code>，<code>y</code>等操作时，内容会存储在匿名寄存器<code>&quot;&quot;</code>中，如果操作指定寄存器<code>&quot;add</code>，除了<code>&quot;a</code>寄存器，内容也会存入匿名寄存器中。p(aste)默认使用匿名寄存器。</p><h4 id="数字寄存器"><a href="#数字寄存器" class="headerlink" title="数字寄存器"></a>数字寄存器</h4><p>“0至”9，其中”0会保留最近一次复制的内容，而”1至 “9则会保存最近9次删除操作的内容。当指定拷贝操作的寄存器时，不会写入”0中，但删除操作一定会写入”1中。使用y(ank)命令未指定寄存器时，会将内容保存至”0寄存器，同时””具有相同的内容。</p><h4 id="短删除寄存器"><a href="#短删除寄存器" class="headerlink" title="短删除寄存器"></a>短删除寄存器</h4><p>顾名思义，对于少于一行的内容操作，除非指定寄存器，否则内容都会被存入 <code>&quot;-</code> 寄存器中。</p><h4 id="具名寄存器"><a href="#具名寄存器" class="headerlink" title="具名寄存器"></a>具名寄存器</h4><p>通过26个英文字母，指定寄存器，例如”add ，如果原有”a寄存器中含有内容，则会被覆盖，而执行”Add则会在原有内容后追加内容。</p><h4 id="只读寄存器"><a href="#只读寄存器" class="headerlink" title="只读寄存器"></a>只读寄存器</h4><p><code>&quot;:, &quot;., &quot;%</code>，这三种寄存器读取内容，其中<code>&quot;:</code>  表示上次执行的命令行，<code>&quot;%</code> 表示当前编辑文件名，<code>&quot;.</code>  表示最近插入的内容。</p><h4 id="交替文件寄存器"><a href="#交替文件寄存器" class="headerlink" title="交替文件寄存器"></a>交替文件寄存器</h4><p>一般我们会使用<code>ctrl-^</code>来回切换buffer，其中原理则是在<code>&quot;#</code>寄存器中存放交替文件。</p><h4 id="表达式寄存器"><a href="#表达式寄存器" class="headerlink" title="表达式寄存器"></a>表达式寄存器</h4><p>normal模式下，输入<code>&quot;=</code>，在命令行中输入任何Vim script的表达式，通过使用p(aste)，插入表达式结果。</p><h4 id="选择拖入寄存器"><a href="#选择拖入寄存器" class="headerlink" title="选择拖入寄存器"></a>选择拖入寄存器</h4><p><code>&quot;*</code> 与 <code>&quot;+</code> 寄存器, 对于maxOS与windows系统都可以表示为系统粘贴板，而在linux中，会有两种不同的意义，使用<code>&quot;+yy</code>，则可以将vim中的文本，粘贴至其他程序，其他程序复制的内容，也会同步到该寄存器中。而当有文本被拖入Vim中时，文本将会存储在<code>&quot;~</code>寄存器中</p><h4 id="黑洞寄存器"><a href="#黑洞寄存器" class="headerlink" title="黑洞寄存器"></a>黑洞寄存器</h4><p><code>&quot;_</code> 对于所有删除或拷贝到该寄存器的内容，都会消失，从而不会影响其他寄存器；例如删除操作，会影响其他数字寄存器，导致原”9寄存器内容被覆盖，而复制操作，则会改变”0寄存器。</p><h4 id="搜索寄存器"><a href="#搜索寄存器" class="headerlink" title="搜索寄存器"></a>搜索寄存器</h4><p>normal模式下，通过<code>/{something}</code>搜索，最近一次的关键词会被存放在<code>&quot;/</code>寄存器中。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过简单了解vim中所有类型的寄存器，大致能解答文章开头的两个问题，也可以实现更多的骚操作。</p><p><strong>Vim编辑的内容如何复制到其他地方？</strong></p><p>通过”+ 或 “* 寄存器，使用系统粘贴板，当然也可以通过设置</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set clipboard=unnamed</span><br></pre></td></tr></table></figure><p><strong>Vim中如何复制一段内容后，多次复用，而不会被替换内容覆盖？</strong></p><p>前文讲到数字寄存器，复制操作时，会保存内容至匿名寄存器以及”0寄存器，而在每次删除操作时，会更新”1-“9寄存器以及匿名寄存器，而p(aste)时，会使用匿名寄存器，所以如果p(aste)操作时，如果有替换内容，匿名寄存器内容会变为替换的内容，不过”0寄存器还是最近一次y(arn)的内容，所以可以使用<code>&quot;0p</code>。或者设置一个map</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xnoremap p "0p</span><br></pre></td></tr></table></figure><p>不过这样会导致 d(elete)操作的内容无法被p(aste)使用，所以只需要使用p(aste)时，替换内容不覆盖匿名寄存器即可。前文有讲到黑洞寄存器<code>&quot;_</code>，不保留不需要的内容，所以我们只需要魔改p(aste)操作，将替换内容删除至黑洞寄存器中，再粘贴内容即可。map如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xnoremap p "_dP  # 这里因为先删除，所以需要使用大写P</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><blockquote><p><a href="http://einverne.github.io/post/2017/11/vim-registers.html" target="_blank" rel="noopener">http://einverne.github.io/post/2017/11/vim-registers.html</a></p><p>vim  :help registers</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;刚接触vim时，相信多多少少都会遇到关于复制粘贴的问题，例如以下两个问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vim编辑的内容如何复制到其他地方？&lt;/li&gt;
&lt;li&gt;Vim中如何复制(y)一段内容后，多次复用，而不会被替换内容覆盖？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实这些问题都可能跟V
      
    
    </summary>
    
    
    
      <category term="vim" scheme="https://www.wzmmmmj.com/tags/vim/"/>
    
  </entry>
  
  <entry>
    <title>记一次优化Vim启动速度</title>
    <link href="https://www.wzmmmmj.com/2020/03/28/vim-startuptime-optimize/"/>
    <id>https://www.wzmmmmj.com/2020/03/28/vim-startuptime-optimize/</id>
    <published>2020-03-28T23:55:57.000Z</published>
    <updated>2022-04-05T08:05:52.188Z</updated>
    
    <content type="html"><![CDATA[<p>最近宅家捣鼓了一段时间Vim，原打算试试Vim作为开发IDE，随着Vim Plugin增多，基本有个IDE的雏形，开发体验上有所改善，但也导致一个问题，启动Vim时变慢了，所以又花了点时间捣鼓优化。（PS：用什么Vim当IDE，Pycharm不香吗?）</p><h2 id="startuptime"><a href="#startuptime" class="headerlink" title="startuptime"></a>startuptime</h2><p>在Vim7.2版本后自带了startuptime参数，可以输出Vim启动过程中的耗时</p><figure class="highlight shell"><table><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">--startuptime &#123;fname&#125;                                   --startuptime</span><br><span class="line">      During startup write timing messages to the file &#123;fname&#125;.</span><br><span class="line">      This can be used to find out where time is spent while loading</span><br><span class="line">      your .vimrc, plugins and opening the first file.</span><br><span class="line">      When &#123;fname&#125; already exists new messages are appended.</span><br><span class="line">      &#123;only available when compiled with the +startuptime</span><br><span class="line">      feature&#125;</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">vim --startuptime time.log</span><br><span class="line">vim --startuptime time.log ~/.vimrc</span><br></pre></td></tr></table></figure><p>这里先上一组优化前的启动耗时</p><ul><li>空白文件</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">221.131  000.229  000.229: sourcing &#x2F;Users&#x2F;a1&#x2F;.vim&#x2F;plugged&#x2F;vim-airline&#x2F;autoload&#x2F;airline&#x2F;extensions&#x2F;tabline&#x2F;builder.vim</span><br><span class="line">244.400  025.347: first screen update</span><br><span class="line">244.402  000.002: --- VIM STARTED ---</span><br></pre></td></tr></table></figure><ul><li>某千行python文件</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">375.848  000.497  000.497: sourcing &#x2F;Users&#x2F;a1&#x2F;.vim&#x2F;plugged&#x2F;vim-gitgutter&#x2F;autoload&#x2F;gitgutter&#x2F;hunk.vim</span><br><span class="line">378.849  085.163: first screen update</span><br><span class="line">378.863  000.014: --- VIM STARTED ---</span><br></pre></td></tr></table></figure><p>此方法能粗略看出哪些操作启动耗时长，只能说够用。以下介绍两种更直观的启动耗时插件，便于优化。</p><h3 id="startuptime-vim"><a href="#startuptime-vim" class="headerlink" title="startuptime.vim"></a>startuptime.vim</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Plug 'tweekmonster/startuptime.vim'</span><br></pre></td></tr></table></figure><p><code>startuptime.vim</code>会对当前文件多次启动，标记启动最慢的一些插件以及输出每个步骤的耗时，安装插件后，通过使用<code>:StartupTime</code>，输出startup-log，如图：</p><p><img src="/images/startuptime.vim.png" alt="startuptime.vim"></p><h3 id="vim-startuptime"><a href="#vim-startuptime" class="headerlink" title="vim-startuptime"></a>vim-startuptime</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Plug 'dstein64/vim-startuptime'</span><br></pre></td></tr></table></figure><p><code>vim-startuptime</code>会返回启动时各个操作之间耗时以及百分比，同样使用<code>:StartupTime</code>，输出如图：</p><p><img src="/images/vim-startuptime.png" alt="vim-startuptime"></p><h3 id="发现问题"><a href="#发现问题" class="headerlink" title="发现问题"></a>发现问题</h3><p>不难看出，拖慢startuptime的几处地方</p><ol><li><p>一些插件，例如nerdtree，vim-airline</p></li><li><p>filetype不对的插件没必要加载，例如打开python文件，显然没必要加载vim-go</p></li><li><p>一些多余的autocommand</p></li></ol><h2 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h2><h3 id="懒加载"><a href="#懒加载" class="headerlink" title="懒加载"></a>懒加载</h3><p>本人配置在vim启动后，nerdtree是处于未展开状态，显然启动时没必要加载，在需要使用加载即可，类似的，当filetype不同时，有些插件也没必要加载。</p><p>使用vim-plug的同学，一定熟悉一些plug option，像branch、do，其中有两种参数可实现lazy load。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">onOn-demand loading: Commands or &lt;Plug&gt;-mappings</span><br><span class="line">forOn-demand loading: File types</span><br></pre></td></tr></table></figure><blockquote><p>另一种插件管理 dein.vim 也可以实现lazy load</p></blockquote><p>下面改写vimrc，对于命令触发的插件添加on参数，而对于fileType触发的则添加for参数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Plug &#39;scrooloose&#x2F;nerdtree&#39;, &#123; &#39;on&#39;: &#39;NERDTreeToggle&#39; &#125;</span><br><span class="line">Plug &#39;fatih&#x2F;vim-go&#39;, &#123; &#39;do&#39;: &#39;:GoUpdateBinaries&#39;, &#39;for&#39;: &#39;go&#39; &#125;</span><br></pre></td></tr></table></figure><p>改动后，startuptime.vim显示如下:</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">Total Time:   62.441 -- Flawless Victory</span><br><span class="line"></span><br><span class="line">Slowest 10 plugins (out of 25)~</span><br><span class="line">  vim-airline12.314</span><br><span class="line">    [runtime]12.211</span><br><span class="line">    [unknown]10.636</span><br><span class="line">     coc.nvim6.997</span><br><span class="line">     ...</span><br></pre></td></tr></table></figure><p>执行<code>vim --startuptime time.log a.py</code></p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">323.386</span>  <span class="number">051.707</span>: <span class="keyword">first</span> screen <span class="keyword">update</span></span><br><span class="line"><span class="number">323.399</span>  <span class="number">000.013</span>: --- VIM STARTED ---</span><br></pre></td></tr></table></figure><blockquote><p>nerdtree通过系统调用获取文件树，类似的还有statusline中获取分支名，这类系统调用的方式都是拖慢启动速度</p></blockquote><h3 id="替换插件"><a href="#替换插件" class="headerlink" title="替换插件"></a>替换插件</h3><p>众所周知，vim-airline是statusline界的集大成者，兼容了许多常用插件，但同时也导致加载vim-airline时，加载不必要的内容，虽然<a href="https://github.com/vim-airline/vim-airline#performance" target="_blank" rel="noopener">vim-airline performance</a> 讲解了如何优化，不过我选择用更轻量的lightline.vim替换vim-airline。</p><figure class="highlight shell"><table><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></pre></td><td class="code"><pre><span class="line">" Plug 'vim-airline/vim-airline'</span><br><span class="line">" Plug 'vim-airline/vim-airline-themes'</span><br><span class="line">Plug 'itchyny/lightline.vim'</span><br><span class="line">Plug 'mengelbrecht/lightline-bufferline'</span><br></pre></td></tr></table></figure><p>如图为使用lightline.vim的statusline，以及配置对比</p><p><img src="/images/lightline.vim.png" alt="lightline.vim"></p><p>再次使用<code>startuptime.vim</code> 以及<code>vim --startuptime time.log a.py</code>，输出如下</p><figure class="highlight plain"><table><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></pre></td><td class="code"><pre><span class="line">Total Time:   55.207 -- Flawless Victory</span><br><span class="line"></span><br><span class="line">Slowest 10 plugins (out of 26)~</span><br><span class="line">    [runtime]12.493</span><br><span class="line">    [unknown]11.405</span><br><span class="line">     coc.nvim7.082</span><br><span class="line">      darcula4.432</span><br><span class="line">          ale3.546</span><br><span class="line">vim-gitgutter3.060</span><br><span class="line">lightline.vim2.748</span><br><span class="line">     ...</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">204.982  025.837: first screen update</span><br><span class="line">204.984  000.002: --- VIM STARTED ---</span><br></pre></td></tr></table></figure><p>启动新文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">121.816  015.576: first screen update</span><br><span class="line">121.818  000.002: --- VIM STARTED ---</span><br></pre></td></tr></table></figure><h2 id="Over"><a href="#Over" class="headerlink" title="Over"></a>Over</h2><p>至此，经过几个步骤之后，启动时间减少了越一半，200ms也还过得去，毕竟不是每次都打开千行的文件。另外对于现如今还不支持异步或者更新不积极的插件，如果有合适的替代品也可以替换，例如fzf.vim或Leaderf替换CtrlP，vista替换tagbar。定期更新vimrc，删除不需要的配置以及插件，有可能会有意外的惊喜哦。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近宅家捣鼓了一段时间Vim，原打算试试Vim作为开发IDE，随着Vim Plugin增多，基本有个IDE的雏形，开发体验上有所改善，但也导致一个问题，启动Vim时变慢了，所以又花了点时间捣鼓优化。（PS：用什么Vim当IDE，Pycharm不香吗?）&lt;/p&gt;
&lt;h2 id
      
    
    </summary>
    
    
    
      <category term="vim" scheme="https://www.wzmmmmj.com/tags/vim/"/>
    
  </entry>
  
  <entry>
    <title>Vim中的Buffers, Windows, Tabs</title>
    <link href="https://www.wzmmmmj.com/2020/01/31/windows-buffers-tabs-in-vim/"/>
    <id>https://www.wzmmmmj.com/2020/01/31/windows-buffers-tabs-in-vim/</id>
    <published>2020-01-31T19:50:06.000Z</published>
    <updated>2022-04-05T08:05:52.189Z</updated>
    
    <content type="html"><![CDATA[<p>Vim中的buffer，tab和window是容易混淆的概念，vim中输入<code>:help window</code>，会对三个概念进行简短解释:</p><blockquote><p>Summary:<br>A buffer is the in-memory text of a file.<br>A window is a viewport on a buffer.<br>A tab page is a collection of windows.</p></blockquote><p>对于常用IDE的同学，如果把这几个概念带入IDE中，常会摸不着头脑 <del>比如我</del>。对照下图，应该更容易理解<br><img src="/images/vim-tabs-windows-buffers.jpg" alt="Tabs Windows Buffers"></p><a id="more"></a><h3 id="Buffers"><a href="#Buffers" class="headerlink" title="Buffers"></a>Buffers</h3><blockquote><p>A buffer is the in-memory text of a file.</p></blockquote><p>Buffer即加载在内存中内容，常为一个文件，并且一个文件只会存在一个buffer。通过<code>vim a.txt b.txt</code>会打开两个文件buffer，同时会在编辑a.txt，接着执行<code>:edit c.txt</code>，窗口会显示c.txt文件，通过 <code>:ls</code> 可以显示所有buffer，这里会显示三行记录。</p><figure class="highlight vim"><table><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></pre></td><td class="code"><pre><span class="line">:<span class="keyword">ls</span></span><br><span class="line">  <span class="number">1</span> #h   <span class="string">"a.txt"</span>                        <span class="built_in">line</span> <span class="number">1</span></span><br><span class="line">  <span class="number">2</span>      <span class="string">"b.txt"</span>                        <span class="built_in">line</span> <span class="number">0</span></span><br><span class="line">  <span class="number">3</span> %<span class="keyword">a</span>   <span class="string">"c.txt"</span>                        <span class="built_in">line</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>可以看到a.txt，c.txt前面会有标识，相关标识含义如下：</p><figure class="highlight plain"><table><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">– （非活动的缓冲区）</span><br><span class="line">a （当前被激活缓冲区）</span><br><span class="line">h （隐藏的缓冲区）</span><br><span class="line">% （当前的缓冲区）</span><br><span class="line"># （交换缓冲区）</span><br><span class="line">&#x3D; （只读缓冲区）</span><br><span class="line">+ （已经更改的缓冲区）</span><br></pre></td></tr></table></figure><p>所以%a表示当前激活的buffer，而#h则表示上一个展示的buffer且是隐藏的。<br>通过<code>:b &lt;编号&gt;</code>或<code>:b &lt;文件全称&gt;</code>可切换到相应的buffer，当然也可以通过<code>:bprev</code>或<code>:bnext</code>前后切换buffer，<code>:bdelete &lt;编号或文件全称&gt;</code>删除某个buffer。如果vimrc中声明<code>set hidden</code>，则在文件修改后未保存可以切换至其他buffer，而如果显式声明<code>set nohidden</code>则需要先保存。</p><h4 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h4><p>如果使用NERDTree时出现:bd退出vim session的时候，可以看看<a href="https://github.com/preservim/nerdtree/issues/400" target="_blank" rel="noopener">此issue</a>，通过<code>:bp&lt;CR&gt;bd #&lt;CR&gt;</code>可以达到删除当前buffer，并回到上一个buffer的效果。</p><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><blockquote><p>A window is a viewport on a buffer.</p></blockquote><p>Window即用于展示buffer的窗口，通过该窗口才能看到buffer中的内容。 使用<code>:split</code>或<code>:vsplit</code>，可实现分屏效果，在新窗口中展示当前buffer，也可在操作后加文件，新窗口中展示新打开的buffer。也就是说，多个window可以展示相同的buffer。</p><h3 id="Tabs"><a href="#Tabs" class="headerlink" title="Tabs"></a>Tabs</h3><blockquote><p>A tab page is a collection of windows.</p></blockquote><p>Tab即多个window的组合，一个tab中可以展示多个window，通过<code>:tabp/:tabn 或 gT/gt</code>前后切换tab，以及<code>:tabc</code>关闭当前tab，更多操作可见<code>:help tab-page</code>。</p><p>使用tab的推荐做法是进行分组管理，例如无关项目或不同类型的代码(server端与client端)都可以在多个tab中管理。在buffer中提到一个文件只会存在一个buffer，所以在多个tab的相同buffer，其实他们是同一个buffer，在某一处被关闭时，其他tab中也相应的不可见。</p><p>当然，不同人对三者的看法与使用习惯是不同的，例如有些人喜欢一个tab对应一个buffer，类似IDE中的布局，也有人不使用tab，对于什么场景下使用buffer还是tab，因人而异，一句话就是怎么舒服怎么来。</p><blockquote><p>参考：<br><a href="https://stackoverflow.com/questions/26708822/why-do-vim-experts-prefer-buffers-over-tabs" target="_blank" rel="noopener">buffers or tabs</a><br><a href="https://stackoverflow.com/questions/102384/using-vims-tabs-like-buffers/103590#103590" target="_blank" rel="noopener">using vim tabs like buffers</a><br><a href="https://sanctum.geek.nz/arabesque/buffers-windows-tabs/" target="_blank" rel="noopener">buffers windows tabs</a><br><a href="https://joshldavis.com/2014/04/05/vim-tab-madness-buffers-vs-tabs/" target="_blank" rel="noopener">vim tab madness buffers vs tabs</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Vim中的buffer，tab和window是容易混淆的概念，vim中输入&lt;code&gt;:help window&lt;/code&gt;，会对三个概念进行简短解释:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Summary:&lt;br&gt;A buffer is the in-memory text of a file.&lt;br&gt;A window is a viewport on a buffer.&lt;br&gt;A tab page is a collection of windows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于常用IDE的同学，如果把这几个概念带入IDE中，常会摸不着头脑 &lt;del&gt;比如我&lt;/del&gt;。对照下图，应该更容易理解&lt;br&gt;&lt;img src=&quot;/images/vim-tabs-windows-buffers.jpg&quot; alt=&quot;Tabs Windows Buffers&quot;&gt;&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="vim" scheme="https://www.wzmmmmj.com/tags/vim/"/>
    
  </entry>
  
  <entry>
    <title>Django Model中的ManyToManyField</title>
    <link href="https://www.wzmmmmj.com/2019/12/14/Django-Model-ManyToManyField/"/>
    <id>https://www.wzmmmmj.com/2019/12/14/Django-Model-ManyToManyField/</id>
    <published>2019-12-14T21:35:11.000Z</published>
    <updated>2022-04-05T08:05:52.179Z</updated>
    
    <content type="html"><![CDATA[<p>日常开发中，设计表结构经常会遇到多对多关系，都会立刻想到<code>ManyToManyField</code>，例如用户收藏门店，用户可以收藏多个门店，门店也可以被多个用户收藏，这个场景就很适合使用<code>ManyToManyField</code>。Model如下：</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Account</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">20</span>, help_text=<span class="string">"用户名"</span>)</span><br><span class="line">   </span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Shop</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">20</span>, help_text=<span class="string">"门店名"</span>)</span><br><span class="line">    collectors = models.ManyToManyField(Account, help_text=<span class="string">"收藏用户"</span>)</span><br></pre></td></tr></table></figure><a id="more"></a><p>依次完成makemigration跟migrate后，创建几条记录</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line">kfc = Shop.objects.create(name=<span class="string">"kfc"</span>)</span><br><span class="line">alice = Account.objects.create(name=<span class="string">"alice"</span>)</span><br><span class="line">kfc.collectors.add(alice)</span><br><span class="line">kfc.collectors.create(name=<span class="string">"bob"</span>)</span><br></pre></td></tr></table></figure><p>通过<code>bob.shops.all()</code>或<code>kfc.accounts.all()</code>即可获取bob收藏的门店和收藏kfc的所有用户。但如果添加数据时，使用以下步骤，则会抛错ValueError</p><figure class="highlight python"><table><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">carol = Account(name=<span class="string">"carol"</span>)</span><br><span class="line">kfc.collectors.add(carol)</span><br><span class="line"><span class="comment"># ValueError: Cannot add "&lt;Account: Account object (None)&gt;"</span></span><br><span class="line"><span class="comment"># : instance is on database "default", value is on database "None"</span></span><br><span class="line"></span><br><span class="line">burger_king = Shops(name=<span class="string">"burger king"</span>)</span><br><span class="line">burger_king.collectos.add(alice)  <span class="comment"># ValueError</span></span><br></pre></td></tr></table></figure><p>此时查看数据库tables，不难发现，Django通过ManyToManyField生成了一张中间表名为<code>*_shop_collectors</code>的中间表。上面操作报错也就不难理解，因为当前carol和burger_king还未存在db中。</p><h3 id="“through”-model"><a href="#“through”-model" class="headerlink" title="“through” model"></a>“through” model</h3><p>如果需求变更，还需要记录用户收藏门店的时间，只使用默认的ManyToManyField显然是不够的。ManyToManyField提供了“through”字段，支持自定义中间表</p><ol><li>将默认中间表写成Django Model</li></ol><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Shop</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">20</span>, help_text=<span class="string">"门店名"</span>)</span><br><span class="line">    collectors = models.ManyToManyField(Account, through=<span class="string">"ShopCollector"</span>)</span><br><span class="line">    </span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShopCollector</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        db_table = <span class="string">"&lt;table_name&gt;"</span></span><br><span class="line">    account = models.ForeignKey(Account, on_delete=models.CASCADE)</span><br><span class="line">    shop = models.ForeignKey(Shop, on_delete=models.CASCADE)</span><br></pre></td></tr></table></figure><blockquote><p>中间表未生成，可以跳过第二步，如果需要在原有中间表上修改，db_table需要是原表名</p></blockquote><ol start="2"><li>执行<code>makemigration</code>，修改生成的mi文件</li></ol><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line">state_operations = [...]  <span class="comment"># 原operations改为state_operations</span></span><br><span class="line">operations = [</span><br><span class="line">    migrations.SeparateDatabaseAndState(state_operations=state_operations)</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ol start="3"><li>加上collect_at字段，依次执行makemigrations，migrate</li></ol><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShopCollector</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        db_table = <span class="string">"&lt;table_name&gt;"</span></span><br><span class="line">    account = models.ForeignKey(Account, on_delete=models.CASCADE)</span><br><span class="line">    shop = models.ForeignKey(Shop, on_delete=models.CASCADE)</span><br><span class="line">    collect_at = models.DateTimeField(auto_now_add=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><ol start="4"><li>依次执行makemigrations，migrate</li></ol><p>几步操作完后，新建collector时，都会附带收藏时间</p><h3 id="through-field"><a href="#through-field" class="headerlink" title="through_field"></a>through_field</h3><p>ManyToManyField生成的中间表只会有两个外键，从而Django可知哪两个model存在多对多关系，但使用自定义中间表时，新增了外键，如下存在两个除Shop表的两个外键：</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShopCollector</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    shop = models.ForeignKey(Shop, on_delete=models.CASADE)</span><br><span class="line">    account = models.ForeignKey(Account, on_delete=models.CASCADE)</span><br><span class="line">    inviter = models.ForeignKey(</span><br><span class="line">        Account, </span><br><span class="line">        related_name=<span class="string">"invite_collectors"</span>,</span><br><span class="line">        on_delete=models.CASADE,</span><br><span class="line">    )</span><br><span class="line">    collect_at = models.DateTimeField(auto_now_add=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>这种情况需要通过设置<code>through_field</code>为两个参数组成的元祖(field1, field2)，并且第一个元素需要是定义ManyToManyField表的外键字段名，声明多对多关系的两个外键，这个例子需要将collectors改为</p><figure class="highlight python"><table><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">collectors = models.ManyToManyField(</span><br><span class="line">    Account, </span><br><span class="line">    through=<span class="string">"ShopCollector"</span>, </span><br><span class="line">    through_fields=(<span class="string">"shop"</span>, <span class="string">"account"</span>),</span><br><span class="line">)</span><br></pre></td></tr></table></figure><blockquote><p>对于自关联的自定义中间表，最好也通过through_fields显示声明</p></blockquote><h3 id="symmetrical"><a href="#symmetrical" class="headerlink" title="symmetrical"></a>symmetrical</h3><p>当使用自关联的多对多关系时，如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Account</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">20</span>, help_text=<span class="string">"用户名"</span>)</span><br><span class="line">    followers = ManyToManyField(<span class="string">"self"</span>, related_name=<span class="string">"followees"</span>)</span><br></pre></td></tr></table></figure><p>此时所有Account的followers都是对称效果，也就是bob是alice的朋友，那alice也是bob的朋友，而且此时reverse relationship无效，不过可以通过设置<code>symmetrical=False</code>取消，使用reverse relatationship，例如这里的followers和followees。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日常开发中，设计表结构经常会遇到多对多关系，都会立刻想到&lt;code&gt;ManyToManyField&lt;/code&gt;，例如用户收藏门店，用户可以收藏多个门店，门店也可以被多个用户收藏，这个场景就很适合使用&lt;code&gt;ManyToManyField&lt;/code&gt;。Model如下：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; django.db &lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; models&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;class&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(models.Model)&lt;/span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    name = models.CharField(max_length=&lt;span class=&quot;number&quot;&gt;20&lt;/span&gt;, help_text=&lt;span class=&quot;string&quot;&gt;&quot;用户名&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;class&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;Shop&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(models.Model)&lt;/span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    name = models.CharField(max_length=&lt;span class=&quot;number&quot;&gt;20&lt;/span&gt;, help_text=&lt;span class=&quot;string&quot;&gt;&quot;门店名&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    collectors = models.ManyToManyField(Account, help_text=&lt;span class=&quot;string&quot;&gt;&quot;收藏用户&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
    
      <category term="Django" scheme="https://www.wzmmmmj.com/tags/Django/"/>
    
      <category term="Python" scheme="https://www.wzmmmmj.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>为什么Django prefetch_related不起作用</title>
    <link href="https://www.wzmmmmj.com/2019/11/24/why-prefetch-related-dont-worked/"/>
    <id>https://www.wzmmmmj.com/2019/11/24/why-prefetch-related-dont-worked/</id>
    <published>2019-11-24T16:53:25.000Z</published>
    <updated>2022-04-05T08:05:52.189Z</updated>
    
    <content type="html"><![CDATA[<p>最近划水刷V站的时候，刷到<a href="https://www.v2ex.com/t/618531" target="_blank" rel="noopener">一个关于prefetch_related的问题</a>，简答了一下，虽然过程有点戏剧，不过让我重新看了一遍<a href="https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related" target="_blank" rel="noopener">官方文档</a>，对prefetch_related也有了新的认识。对于刚接触prefetch_related，如果不了解其工作原理，相信都会遇到标题所示的问题。<br><strong>为什么我用了prefetch_related查询次数没减少，反而还多了？</strong></p><p>其实答案就在官方文档中</p><blockquote><p>Remember that, as always with QuerySets, any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query</p></blockquote><a id="more"></a><p>举个官方例子</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Topping</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Pizza</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">50</span>)</span><br><span class="line">    toppings = models.ManyToManyField(Topping)</span><br></pre></td></tr></table></figure><p>如果需要查询每个Pizza下的所有Toppings，如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pizzas = Pizza.objects.prefetch_related(<span class="string">"toppings"</span>).all()</span><br></pre></td></tr></table></figure><p>实际上这条语句除了执行 <code>SELECT &quot;pizza&quot;.&quot;id&quot;, &quot;pizza&quot;.&quot;name&quot; FROM &quot;pizza&quot;</code><br>因为加了prefetch_relate语句还会另外执行一条sql，通过in语句，将符合条件的pizza中所有toppings加载至内存中</p><figure class="highlight sql"><table><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"><span class="keyword">SELECT</span></span><br><span class="line">  (<span class="string">"pizza_toppings"</span>.<span class="string">"pizza_id"</span>) <span class="keyword">AS</span> <span class="string">"_prefetch_related_val_pizza_id"</span>, </span><br><span class="line">  <span class="string">"topping"</span>.<span class="string">"id"</span>, <span class="string">"topping"</span>.<span class="string">"name"</span> </span><br><span class="line"><span class="keyword">FROM</span> <span class="string">"topping"</span> </span><br><span class="line"><span class="keyword">INNER</span> <span class="keyword">JOIN</span> <span class="string">"pizza_toppings"</span> </span><br><span class="line"><span class="keyword">ON</span> (<span class="string">"topping"</span>.<span class="string">"id"</span> = <span class="string">"pizza_toppings"</span>.<span class="string">"topping_id"</span>) </span><br><span class="line"><span class="keyword">WHERE</span> <span class="string">"pizza_toppings"</span>.<span class="string">"pizza_id"</span> <span class="keyword">IN</span> (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br></pre></td></tr></table></figure><p>所以通过执行类似<code>pizza.toppings.all()</code>的语句，实际上就会从缓存中读取，而不会产生sql，从而达到查询优化。但有时候总会有一些奇奇怪怪的需求。如果只想把topping.name=’top’的读取出来，如果我们使用了这样的语句<code>pizza.toppings.filter(name=&#39;top&#39;)</code>，就会与上诉缓存不一致，从而再次执行一条sql，不但没有产生任何优化，反而因为用了prefetch_related，还多了一次查询。</p><blockquote><p>除了filter()，order_by()，first() 也会因为查询不一致，用不上缓存，而count()则不会。如果不确定是否一致，可以输出语句执行sql瞅一瞅。如果使用类似<code>pizza.toppings.add()</code>等alter语句会使prefetch_related缓存被清除，从而导致查询优化失效，这种情况可以通过改变代码逻辑。</p></blockquote><p>遇到这种情况，可以选择在内存中进行后续操作</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># filter</span></span><br><span class="line">toppings = [</span><br><span class="line">    topping</span><br><span class="line">    <span class="keyword">for</span> topping <span class="keyword">in</span> pizza.toppings.all()</span><br><span class="line">    <span class="keyword">if</span> topping.name == <span class="string">'top'</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># first</span></span><br><span class="line">topping = pizza.topping[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># order_by</span></span><br><span class="line">toppings = sorted(pizza.toppings.all(), key=<span class="keyword">lambda</span> _: _.id, reversed=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>当然还可以通过自定义prefetch_related生成的sql，缓存需要的queryset</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db.models <span class="keyword">import</span> Prefetch</span><br><span class="line"></span><br><span class="line">filters = Topping.objects.filter(name=<span class="string">'top'</span>)</span><br><span class="line">pizzas = Pizza.objects.prefetch_related(Prefetch(<span class="string">"toppings"</span>, queryset=filters))</span><br></pre></td></tr></table></figure><p>如果想对同一个外键进行不用的filter，还可以使用参数to_attr命名</p><figure class="highlight python"><table><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></pre></td><td class="code"><pre><span class="line">filters = Topping.objects.filter(name=<span class="string">'top'</span>)</span><br><span class="line">order = Topping.objects.order_by(<span class="string">'-id'</span>)</span><br><span class="line">pizzas = Pizza.objects.prefetch_related(</span><br><span class="line">    Prefetch(<span class="string">"toppings"</span>, queryset=filters, to_attr=<span class="string">'filter_toppings'</span>),</span><br><span class="line">    Prefetch(<span class="string">"toppings"</span>, queryset=order, to_attr=<span class="string">'order_toppings'</span>),</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>这样通过pizzas.order_toppings.all()或pizzas.filter_toppings.all()使用即可</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近划水刷V站的时候，刷到&lt;a href=&quot;https://www.v2ex.com/t/618531&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;一个关于prefetch_related的问题&lt;/a&gt;，简答了一下，虽然过程有点戏剧，不过让我重新看了一遍&lt;a href=&quot;https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;官方文档&lt;/a&gt;，对prefetch_related也有了新的认识。对于刚接触prefetch_related，如果不了解其工作原理，相信都会遇到标题所示的问题。&lt;br&gt;&lt;strong&gt;为什么我用了prefetch_related查询次数没减少，反而还多了？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实答案就在官方文档中&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember that, as always with QuerySets, any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
    
      <category term="Django" scheme="https://www.wzmmmmj.com/tags/Django/"/>
    
      <category term="Python" scheme="https://www.wzmmmmj.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>不同目录配置不同git config</title>
    <link href="https://www.wzmmmmj.com/2019/09/22/git-config-include/"/>
    <id>https://www.wzmmmmj.com/2019/09/22/git-config-include/</id>
    <published>2019-09-22T16:44:52.000Z</published>
    <updated>2022-04-05T08:05:52.183Z</updated>
    
    <content type="html"><![CDATA[<p>日常使用git时，一般会有全局配置文件的<code>.gitconfig</code>，所有repo会默认使用这个配置。如果需要特殊配置某个repo，只需要修改repo里的<code>.git/config</code>文件即可。但如果需要修改repo数量变多就容易忘，导致提交错误author的commit。</p><h3 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h3><p>例如，公司repo与个人repo，想使用不同的<code>user.name</code>与<code>user.email</code>，简单操作就是，修改个人repo中<code>.git/config</code>中的配置</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[user]</span><br><span class="line">    name = name1</span><br><span class="line">    email = name1@example.com</span><br></pre></td></tr></table></figure><p>或执行以下语句，修改配置（两种方法本质上是一样的）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config user.name name1</span><br><span class="line">git config user.email name1@example.com</span><br></pre></td></tr></table></figure><p>这样就会覆盖全局配置，但repo一多，这种方法就显得效率很低。</p><a id="more"></a><h3 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h3><p>个人会用不同的文件夹区分公司与个人的repo，这样就可以通过<a href="https://git-scm.com/docs/git-config#_conditional_includes" target="_blank" rel="noopener">git conditional include</a>，给不同目录下的repo，设置不同的<code>git config</code>。假设公司repo，会放在<code>work</code>目录下。</p><p>新建一个<code>.gitconfig-work</code>，内容如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[user]</span><br><span class="line">    name = name2</span><br><span class="line">    email = name2@example.com</span><br></pre></td></tr></table></figure><p>打开全局配置<code>.gitconfig</code>文件，添加目录配置</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[includeIf "gitdir:~/work/"]</span><br><span class="line">    path = .gitconfig-work</span><br></pre></td></tr></table></figure><p>这样<code>work</code>下的新repo，都会使用<code>.gitconfig-work</code>中的配置覆盖全局配置，从而达到不同目录使用不同的<code>git config</code></p><h3 id="Tip"><a href="#Tip" class="headerlink" title="Tip"></a>Tip</h3><p>对于work目录下已存在的repo，可执行以下命令查看<code>.gitconfig-work</code>中配置是否生效</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --show-origin --get user.name</span><br><span class="line">git config --show-origin --get xxxx</span><br></pre></td></tr></table></figure><blockquote><p>参考</p><p><a href="https://git-scm.com/docs/git-config#_conditional_includes" target="_blank" rel="noopener">https://git-scm.com/docs/git-config#_conditional_includes</a></p><p><a href="https://stackoverflow.com/questions/43919191/git-2-13-conditional-config-on-windows" target="_blank" rel="noopener">https://stackoverflow.com/questions/43919191/git-2-13-conditional-config-on-windows</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;日常使用git时，一般会有全局配置文件的&lt;code&gt;.gitconfig&lt;/code&gt;，所有repo会默认使用这个配置。如果需要特殊配置某个repo，只需要修改repo里的&lt;code&gt;.git/config&lt;/code&gt;文件即可。但如果需要修改repo数量变多就容易忘，导致提交错误author的commit。&lt;/p&gt;
&lt;h3 id=&quot;Demo&quot;&gt;&lt;a href=&quot;#Demo&quot; class=&quot;headerlink&quot; title=&quot;Demo&quot;&gt;&lt;/a&gt;Demo&lt;/h3&gt;&lt;p&gt;例如，公司repo与个人repo，想使用不同的&lt;code&gt;user.name&lt;/code&gt;与&lt;code&gt;user.email&lt;/code&gt;，简单操作就是，修改个人repo中&lt;code&gt;.git/config&lt;/code&gt;中的配置&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;[user]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    name = name1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    email = name1@example.com&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;或执行以下语句，修改配置（两种方法本质上是一样的）&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git config user.name name1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;git config user.email name1@example.com&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这样就会覆盖全局配置，但repo一多，这种方法就显得效率很低。&lt;/p&gt;
    
    </summary>
    
    
      <category term="工具" scheme="https://www.wzmmmmj.com/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
      <category term="git" scheme="https://www.wzmmmmj.com/tags/git/"/>
    
  </entry>
  
</feed>
